See More

/* esm-bundle - @angular/[email protected] - Ivy - system format - 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 */ System.register(['rxjs', 'rxjs/operators'], (function (exports) { 'use strict'; var BehaviorSubject, Observable, merge$1, of, Subject, Subscription, share, switchMap, distinctUntilChanged, first; return { setters: [function (module) { BehaviorSubject = module.BehaviorSubject; Observable = module.Observable; merge$1 = module.merge; of = module.of; Subject = module.Subject; Subscription = module.Subscription; }, function (module) { share = module.share; switchMap = module.switchMap; distinctUntilChanged = module.distinctUntilChanged; first = module.first; }], execute: (function () { exports({ asNativeElements: asNativeElements, assertInInjectionContext: assertInInjectionContext, assertPlatform: assertPlatform, computed: computed, createComponent: createComponent, createEnvironmentInjector: createEnvironmentInjector, createNgModule: createNgModule, createPlatform: createPlatform, createPlatformFactory: createPlatformFactory, destroyPlatform: destroyPlatform, effect: effect, enableProdMode: enableProdMode, forwardRef: forwardRef, getDebugNode: getDebugNode, getModuleFactory: getModuleFactory, getNgModuleById: getNgModuleById, getPlatform: getPlatform, importProvidersFrom: importProvidersFrom, inject: inject, isDevMode: isDevMode, isSignal: isSignal, isStandalone: isStandalone, makeEnvironmentProviders: makeEnvironmentProviders, makeStateKey: makeStateKey, mergeApplicationConfig: mergeApplicationConfig, provideZoneChangeDetection: provideZoneChangeDetection, reflectComponentType: reflectComponentType, resolveForwardRef: resolveForwardRef, runInInjectionContext: runInInjectionContext, setTestabilityGetter: setTestabilityGetter, signal: signal, untracked: untracked, ɵLifecycleHooksFeature: LifecycleHooksFeature, ɵ_sanitizeHtml: _sanitizeHtml, ɵ_sanitizeUrl: _sanitizeUrl, ɵallowSanitizationBypassAndThrow: allowSanitizationBypassAndThrow, ɵannotateForHydration: annotateForHydration, ɵbypassSanitizationTrustHtml: bypassSanitizationTrustHtml, ɵbypassSanitizationTrustResourceUrl: bypassSanitizationTrustResourceUrl, ɵbypassSanitizationTrustScript: bypassSanitizationTrustScript, ɵbypassSanitizationTrustStyle: bypassSanitizationTrustStyle, ɵbypassSanitizationTrustUrl: bypassSanitizationTrustUrl, ɵclearResolutionOfComponentResourcesQueue: clearResolutionOfComponentResourcesQueue, ɵcoerceToBoolean: coerceToBoolean, ɵcompileComponent: compileComponent, ɵcompileDirective: compileDirective, ɵcompileNgModule: compileNgModule, ɵcompileNgModuleDefs: compileNgModuleDefs, ɵcompileNgModuleFactory: compileNgModuleFactory, ɵcompilePipe: compilePipe, ɵconvertToBitFlags: convertToBitFlags, ɵcreateInjector: createInjector, ɵdetectChanges: detectChanges, ɵdevModeEqual: devModeEqual, ɵfindLocaleData: findLocaleData, ɵflushModuleScopingQueueAsMuchAsPossible: flushModuleScopingQueueAsMuchAsPossible, ɵformatRuntimeError: formatRuntimeError, ɵgetDebugNode: getDebugNode, ɵgetDirectives: getDirectives, ɵgetHostElement: getHostElement, ɵgetInjectableDef: getInjectableDef, ɵgetLContext: getLContext, ɵgetLocaleCurrencyCode: getLocaleCurrencyCode, ɵgetLocalePluralCase: getLocalePluralCase, ɵgetSanitizationBypassType: getSanitizationBypassType, ɵgetUnknownElementStrictMode: ɵgetUnknownElementStrictMode, ɵgetUnknownPropertyStrictMode: ɵgetUnknownPropertyStrictMode, ɵinjectChangeDetectorRef: injectChangeDetectorRef, ɵinternalCreateApplication: internalCreateApplication, ɵisBoundToModule: isBoundToModule, ɵisEnvironmentProviders: isEnvironmentProviders, ɵisInjectable: isInjectable, ɵisNgModule: isNgModule, ɵisPromise: isPromise, ɵisSubscribable: isSubscribable, ɵnoSideEffects: noSideEffects, ɵpatchComponentDefWithScope: patchComponentDefWithScope, ɵpublishDefaultGlobalUtils: publishDefaultGlobalUtils$1, ɵpublishGlobalUtil: publishGlobalUtil, ɵregisterLocaleData: registerLocaleData, ɵresetCompiledComponents: resetCompiledComponents, ɵresetJitOptions: resetJitOptions, ɵresolveComponentResources: resolveComponentResources, ɵsetAllowDuplicateNgModuleIdsForTest: setAllowDuplicateNgModuleIdsForTest, ɵsetAlternateWeakRefImpl: setAlternateWeakRefImpl, ɵsetClassMetadata: setClassMetadata, ɵsetCurrentInjector: setCurrentInjector, ɵsetDocument: setDocument, ɵsetLocaleId: setLocaleId, ɵsetUnknownElementStrictMode: ɵsetUnknownElementStrictMode, ɵsetUnknownPropertyStrictMode: ɵsetUnknownPropertyStrictMode, ɵstore: store, ɵstringify: stringify, ɵtransitiveScopesFor: transitiveScopesFor, ɵunregisterLocaleData: unregisterAllLocaleData, ɵunwrapSafeValue: unwrapSafeValue, ɵwithDomHydration: withDomHydration, ɵɵCopyDefinitionFeature: ɵɵCopyDefinitionFeature, ɵɵHostDirectivesFeature: ɵɵHostDirectivesFeature, ɵɵInheritDefinitionFeature: ɵɵInheritDefinitionFeature, ɵɵNgOnChangesFeature: ɵɵNgOnChangesFeature, ɵɵProvidersFeature: ɵɵProvidersFeature, ɵɵStandaloneFeature: ɵɵStandaloneFeature, ɵɵadvance: ɵɵadvance, ɵɵattribute: ɵɵattribute, ɵɵattributeInterpolate1: ɵɵattributeInterpolate1, ɵɵattributeInterpolate2: ɵɵattributeInterpolate2, ɵɵattributeInterpolate3: ɵɵattributeInterpolate3, ɵɵattributeInterpolate4: ɵɵattributeInterpolate4, ɵɵattributeInterpolate5: ɵɵattributeInterpolate5, ɵɵattributeInterpolate6: ɵɵattributeInterpolate6, ɵɵattributeInterpolate7: ɵɵattributeInterpolate7, ɵɵattributeInterpolate8: ɵɵattributeInterpolate8, ɵɵattributeInterpolateV: ɵɵattributeInterpolateV, ɵɵclassMap: ɵɵclassMap, ɵɵclassMapInterpolate1: ɵɵclassMapInterpolate1, ɵɵclassMapInterpolate2: ɵɵclassMapInterpolate2, ɵɵclassMapInterpolate3: ɵɵclassMapInterpolate3, ɵɵclassMapInterpolate4: ɵɵclassMapInterpolate4, ɵɵclassMapInterpolate5: ɵɵclassMapInterpolate5, ɵɵclassMapInterpolate6: ɵɵclassMapInterpolate6, ɵɵclassMapInterpolate7: ɵɵclassMapInterpolate7, ɵɵclassMapInterpolate8: ɵɵclassMapInterpolate8, ɵɵclassMapInterpolateV: ɵɵclassMapInterpolateV, ɵɵclassProp: ɵɵclassProp, ɵɵcontentQuery: ɵɵcontentQuery, ɵɵdefineComponent: ɵɵdefineComponent, ɵɵdefineDirective: ɵɵdefineDirective, ɵɵdefineInjectable: ɵɵdefineInjectable, ɵɵdefineInjector: ɵɵdefineInjector, ɵɵdefineNgModule: ɵɵdefineNgModule, ɵɵdefinePipe: ɵɵdefinePipe, ɵɵdirectiveInject: ɵɵdirectiveInject, ɵɵdisableBindings: ɵɵdisableBindings, ɵɵelement: ɵɵelement, ɵɵelementContainer: ɵɵelementContainer, ɵɵelementContainerEnd: ɵɵelementContainerEnd, ɵɵelementContainerStart: ɵɵelementContainerStart, ɵɵelementEnd: ɵɵelementEnd, ɵɵelementStart: ɵɵelementStart, ɵɵenableBindings: ɵɵenableBindings, ɵɵgetCurrentView: ɵɵgetCurrentView, ɵɵgetInheritedFactory: ɵɵgetInheritedFactory, ɵɵhostProperty: ɵɵhostProperty, ɵɵi18n: ɵɵi18n, ɵɵi18nApply: ɵɵi18nApply, ɵɵi18nAttributes: ɵɵi18nAttributes, ɵɵi18nEnd: ɵɵi18nEnd, ɵɵi18nExp: ɵɵi18nExp, ɵɵi18nPostprocess: ɵɵi18nPostprocess, ɵɵi18nStart: ɵɵi18nStart, ɵɵinject: ɵɵinject, ɵɵinjectAttribute: ɵɵinjectAttribute, ɵɵinvalidFactory: ɵɵinvalidFactory, ɵɵinvalidFactoryDep: ɵɵinvalidFactoryDep, ɵɵlistener: ɵɵlistener, ɵɵloadQuery: ɵɵloadQuery, ɵɵnamespaceHTML: ɵɵnamespaceHTML, ɵɵnamespaceMathML: ɵɵnamespaceMathML, ɵɵnamespaceSVG: ɵɵnamespaceSVG, ɵɵnextContext: ɵɵnextContext, ɵɵngDeclareClassMetadata: ɵɵngDeclareClassMetadata, ɵɵngDeclareComponent: ɵɵngDeclareComponent, ɵɵngDeclareDirective: ɵɵngDeclareDirective, ɵɵngDeclareFactory: ɵɵngDeclareFactory, ɵɵngDeclareInjectable: ɵɵngDeclareInjectable, ɵɵngDeclareInjector: ɵɵngDeclareInjector, ɵɵngDeclareNgModule: ɵɵngDeclareNgModule, ɵɵngDeclarePipe: ɵɵngDeclarePipe, ɵɵpipe: ɵɵpipe, ɵɵpipeBind1: ɵɵpipeBind1, ɵɵpipeBind2: ɵɵpipeBind2, ɵɵpipeBind3: ɵɵpipeBind3, ɵɵpipeBind4: ɵɵpipeBind4, ɵɵpipeBindV: ɵɵpipeBindV, ɵɵprojection: ɵɵprojection, ɵɵprojectionDef: ɵɵprojectionDef, ɵɵproperty: ɵɵproperty, ɵɵpropertyInterpolate: ɵɵpropertyInterpolate, ɵɵpropertyInterpolate1: ɵɵpropertyInterpolate1, ɵɵpropertyInterpolate2: ɵɵpropertyInterpolate2, ɵɵpropertyInterpolate3: ɵɵpropertyInterpolate3, ɵɵpropertyInterpolate4: ɵɵpropertyInterpolate4, ɵɵpropertyInterpolate5: ɵɵpropertyInterpolate5, ɵɵpropertyInterpolate6: ɵɵpropertyInterpolate6, ɵɵpropertyInterpolate7: ɵɵpropertyInterpolate7, ɵɵpropertyInterpolate8: ɵɵpropertyInterpolate8, ɵɵpropertyInterpolateV: ɵɵpropertyInterpolateV, ɵɵpureFunction0: ɵɵpureFunction0, ɵɵpureFunction1: ɵɵpureFunction1, ɵɵpureFunction2: ɵɵpureFunction2, ɵɵpureFunction3: ɵɵpureFunction3, ɵɵpureFunction4: ɵɵpureFunction4, ɵɵpureFunction5: ɵɵpureFunction5, ɵɵpureFunction6: ɵɵpureFunction6, ɵɵpureFunction7: ɵɵpureFunction7, ɵɵpureFunction8: ɵɵpureFunction8, ɵɵpureFunctionV: ɵɵpureFunctionV, ɵɵqueryRefresh: ɵɵqueryRefresh, ɵɵreference: ɵɵreference, ɵɵregisterNgModuleType: registerNgModuleType, ɵɵresetView: ɵɵresetView, ɵɵresolveBody: ɵɵresolveBody, ɵɵresolveDocument: ɵɵresolveDocument, ɵɵresolveWindow: ɵɵresolveWindow, ɵɵrestoreView: ɵɵrestoreView, ɵɵsanitizeHtml: ɵɵsanitizeHtml, ɵɵsanitizeResourceUrl: ɵɵsanitizeResourceUrl, ɵɵsanitizeScript: ɵɵsanitizeScript, ɵɵsanitizeStyle: ɵɵsanitizeStyle, ɵɵsanitizeUrl: ɵɵsanitizeUrl, ɵɵsanitizeUrlOrResourceUrl: ɵɵsanitizeUrlOrResourceUrl, ɵɵsetComponentScope: ɵɵsetComponentScope, ɵɵsetNgModuleScope: ɵɵsetNgModuleScope, ɵɵstyleMap: ɵɵstyleMap, ɵɵstyleMapInterpolate1: ɵɵstyleMapInterpolate1, ɵɵstyleMapInterpolate2: ɵɵstyleMapInterpolate2, ɵɵstyleMapInterpolate3: ɵɵstyleMapInterpolate3, ɵɵstyleMapInterpolate4: ɵɵstyleMapInterpolate4, ɵɵstyleMapInterpolate5: ɵɵstyleMapInterpolate5, ɵɵstyleMapInterpolate6: ɵɵstyleMapInterpolate6, ɵɵstyleMapInterpolate7: ɵɵstyleMapInterpolate7, ɵɵstyleMapInterpolate8: ɵɵstyleMapInterpolate8, ɵɵstyleMapInterpolateV: ɵɵstyleMapInterpolateV, ɵɵstyleProp: ɵɵstyleProp, ɵɵstylePropInterpolate1: ɵɵstylePropInterpolate1, ɵɵstylePropInterpolate2: ɵɵstylePropInterpolate2, ɵɵstylePropInterpolate3: ɵɵstylePropInterpolate3, ɵɵstylePropInterpolate4: ɵɵstylePropInterpolate4, ɵɵstylePropInterpolate5: ɵɵstylePropInterpolate5, ɵɵstylePropInterpolate6: ɵɵstylePropInterpolate6, ɵɵstylePropInterpolate7: ɵɵstylePropInterpolate7, ɵɵstylePropInterpolate8: ɵɵstylePropInterpolate8, ɵɵstylePropInterpolateV: ɵɵstylePropInterpolateV, ɵɵsyntheticHostListener: ɵɵsyntheticHostListener, ɵɵsyntheticHostProperty: ɵɵsyntheticHostProperty, ɵɵtemplate: ɵɵtemplate, ɵɵtemplateRefExtractor: ɵɵtemplateRefExtractor, ɵɵtext: ɵɵtext, ɵɵtextInterpolate: ɵɵtextInterpolate, ɵɵtextInterpolate1: ɵɵtextInterpolate1, ɵɵtextInterpolate2: ɵɵtextInterpolate2, ɵɵtextInterpolate3: ɵɵtextInterpolate3, ɵɵtextInterpolate4: ɵɵtextInterpolate4, ɵɵtextInterpolate5: ɵɵtextInterpolate5, ɵɵtextInterpolate6: ɵɵtextInterpolate6, ɵɵtextInterpolate7: ɵɵtextInterpolate7, ɵɵtextInterpolate8: ɵɵtextInterpolate8, ɵɵtextInterpolateV: ɵɵtextInterpolateV, ɵɵtrustConstantHtml: ɵɵtrustConstantHtml, ɵɵtrustConstantResourceUrl: ɵɵtrustConstantResourceUrl, ɵɵvalidateIframeAttribute: ɵɵvalidateIframeAttribute, ɵɵviewQuery: ɵɵviewQuery }); /** * @license Angular v16.0.5 * (c) 2010-2022 Google LLC. https://angular.io/ * License: MIT */ function getClosureSafeProperty(objWithPropertyToExtract) { for (let key in objWithPropertyToExtract) { if (objWithPropertyToExtract[key] === getClosureSafeProperty) { return key; } } throw Error('Could not find renamed property on target object.'); } /** * Sets properties on a target object from a source object, but only if * the property doesn't already exist on the target object. * @param target The target to set properties on * @param source The source of the property keys and values to set */ function fillProperties(target, source) { for (const key in source) { if (source.hasOwnProperty(key) && !target.hasOwnProperty(key)) { target[key] = source[key]; } } } function stringify(token) { if (typeof token === 'string') { return token; } if (Array.isArray(token)) { return '[' + token.map(stringify).join(', ') + ']'; } if (token == null) { return '' + token; } if (token.overriddenName) { return `${token.overriddenName}`; } if (token.name) { return `${token.name}`; } const res = token.toString(); if (res == null) { return '' + res; } const newLineIndex = res.indexOf('\n'); return newLineIndex === -1 ? res : res.substring(0, newLineIndex); } /** * Concatenates two strings with separator, allocating new strings only when necessary. * * @param before before string. * @param separator separator string. * @param after after string. * @returns concatenated string. */ function concatStringsWithSpace(before, after) { return (before == null || before === '') ? (after === null ? '' : after) : ((after == null || after === '') ? before : before + ' ' + after); } const __forward_ref__ = getClosureSafeProperty({ __forward_ref__: getClosureSafeProperty }); /** * Allows to refer to references which are not yet defined. * * For instance, `forwardRef` is used when the `token` which we need to refer to for the purposes of * DI is declared, but not yet defined. It is also used when the `token` which we use when creating * a query is not yet defined. * * `forwardRef` is also used to break circularities in standalone components imports. * * @usageNotes * ### Circular dependency example * {@example core/di/ts/forward_ref/forward_ref_spec.ts region='forward_ref'} * * ### Circular standalone reference import example * ```ts * @Component({ * standalone: true, * imports: [ChildComponent], * selector: 'app-parent', * template: ``, * }) * export class ParentComponent { * @Input() hideParent: boolean; * } * * * @Component({ * standalone: true, * imports: [CommonModule, forwardRef(() => ParentComponent)], * selector: 'app-child', * template: ``, * }) * export class ChildComponent { * @Input() hideParent: boolean; * } * ``` * * @publicApi */ function forwardRef(forwardRefFn) { forwardRefFn.__forward_ref__ = forwardRef; forwardRefFn.toString = function () { return stringify(this()); }; return forwardRefFn; } /** * Lazily retrieves the reference value from a forwardRef. * * Acts as the identity function when given a non-forward-ref value. * * @usageNotes * ### Example * * {@example core/di/ts/forward_ref/forward_ref_spec.ts region='resolve_forward_ref'} * * @see `forwardRef` * @publicApi */ function resolveForwardRef(type) { return isForwardRef(type) ? type() : type; } /** Checks whether a function is wrapped by a `forwardRef`. */ function isForwardRef(fn) { return typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) && fn.__forward_ref__ === forwardRef; } function isEnvironmentProviders(value) { return value && !!value.ɵproviders; } /** * Base URL for the error details page. * * Keep this constant in sync across: * - packages/compiler-cli/src/ngtsc/diagnostics/src/error_details_base_url.ts * - packages/core/src/error_details_base_url.ts */ const ERROR_DETAILS_PAGE_BASE_URL = 'https://angular.io/errors'; /** * URL for the XSS security documentation. */ const XSS_SECURITY_URL = exports('ɵXSS_SECURITY_URL', 'https://g.co/ng/security#xss'); /** * Class that represents a runtime error. * Formats and outputs the error message in a consistent way. * * Example: * ``` * throw new RuntimeError( * RuntimeErrorCode.INJECTOR_ALREADY_DESTROYED, * ngDevMode && 'Injector has already been destroyed.'); * ``` * * Note: the `message` argument contains a descriptive error message as a string in development * mode (when the `ngDevMode` is defined). In production mode (after tree-shaking pass), the * `message` argument becomes `false`, thus we account for it in the typings and the runtime * logic. */ class RuntimeError extends Error { constructor(code, message) { super(formatRuntimeError(code, message)); this.code = code; } } exports('ɵRuntimeError', RuntimeError); /** * Called to format a runtime error. * See additional info on the `message` argument type in the `RuntimeError` class description. */ function formatRuntimeError(code, message) { // Error code might be a negative number, which is a special marker that instructs the logic to // generate a link to the error details page on angular.io. // We also prepend `0` to non-compile-time errors. const fullCode = `NG0${Math.abs(code)}`; let errorMessage = `${fullCode}${message ? ': ' + message : ''}`; if (ngDevMode && code < 0) { const addPeriodSeparator = !errorMessage.match(/[.,;!?\n]$/); const separator = addPeriodSeparator ? '.' : ''; errorMessage = `${errorMessage}${separator} Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/${fullCode}`; } return errorMessage; } /** * Used for stringify render output in Ivy. * Important! This function is very performance-sensitive and we should * be extra careful not to introduce megamorphic reads in it. * Check `core/test/render3/perf/render_stringify` for benchmarks and alternate implementations. */ function renderStringify(value) { if (typeof value === 'string') return value; if (value == null) return ''; // Use `String` so that it invokes the `toString` method of the value. Note that this // appears to be faster than calling `value.toString` (see `render_stringify` benchmark). return String(value); } /** * Used to stringify a value so that it can be displayed in an error message. * Important! This function contains a megamorphic read and should only be * used for error messages. */ function stringifyForError(value) { if (typeof value === 'function') return value.name || value.toString(); if (typeof value === 'object' && value != null && typeof value.type === 'function') { return value.type.name || value.type.toString(); } return renderStringify(value); } /** Called when directives inject each other (creating a circular dependency) */ function throwCyclicDependencyError(token, path) { const depPath = path ? `. Dependency path: ${path.join(' > ')} > ${token}` : ''; throw new RuntimeError(-200 /* RuntimeErrorCode.CYCLIC_DI_DEPENDENCY */, `Circular dependency in DI detected for ${token}${depPath}`); } function throwMixedMultiProviderError() { throw new Error(`Cannot mix multi providers and regular providers`); } function throwInvalidProviderError(ngModuleType, providers, provider) { if (ngModuleType && providers) { const providerDetail = providers.map(v => v == provider ? '?' + provider + '?' : '...'); throw new Error(`Invalid provider for the NgModule '${stringify(ngModuleType)}' - only instances of Provider and Type are allowed, got: [${providerDetail.join(', ')}]`); } else if (isEnvironmentProviders(provider)) { if (provider.ɵfromNgModule) { throw new RuntimeError(207 /* RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT */, `Invalid providers from 'importProvidersFrom' present in a non-environment injector. 'importProvidersFrom' can't be used for component providers.`); } else { throw new RuntimeError(207 /* RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT */, `Invalid providers present in a non-environment injector. 'EnvironmentProviders' can't be used for component providers.`); } } else { throw new Error('Invalid provider'); } } /** Throws an error when a token is not found in DI. */ function throwProviderNotFoundError(token, injectorName) { const injectorDetails = injectorName ? ` in ${injectorName}` : ''; throw new RuntimeError(-201 /* RuntimeErrorCode.PROVIDER_NOT_FOUND */, ngDevMode && `No provider for ${stringifyForError(token)} found${injectorDetails}`); } // The functions in this file verify that the assumptions we are making function assertNumber(actual, msg) { if (!(typeof actual === 'number')) { throwError(msg, typeof actual, 'number', '==='); } } function assertNumberInRange(actual, minInclusive, maxInclusive) { assertNumber(actual, 'Expected a number'); assertLessThanOrEqual(actual, maxInclusive, 'Expected number to be less than or equal to'); assertGreaterThanOrEqual(actual, minInclusive, 'Expected number to be greater than or equal to'); } function assertString(actual, msg) { if (!(typeof actual === 'string')) { throwError(msg, actual === null ? 'null' : typeof actual, 'string', '==='); } } function assertFunction(actual, msg) { if (!(typeof actual === 'function')) { throwError(msg, actual === null ? 'null' : typeof actual, 'function', '==='); } } function assertEqual(actual, expected, msg) { if (!(actual == expected)) { throwError(msg, actual, expected, '=='); } } function assertNotEqual(actual, expected, msg) { if (!(actual != expected)) { throwError(msg, actual, expected, '!='); } } function assertSame(actual, expected, msg) { if (!(actual === expected)) { throwError(msg, actual, expected, '==='); } } function assertNotSame(actual, expected, msg) { if (!(actual !== expected)) { throwError(msg, actual, expected, '!=='); } } function assertLessThan(actual, expected, msg) { if (!(actual < expected)) { throwError(msg, actual, expected, '<'); } } function assertLessThanOrEqual(actual, expected, msg) { if (!(actual <= expected)) { throwError(msg, actual, expected, '<='); } } function assertGreaterThan(actual, expected, msg) { if (!(actual > expected)) { throwError(msg, actual, expected, '>'); } } function assertGreaterThanOrEqual(actual, expected, msg) { if (!(actual >= expected)) { throwError(msg, actual, expected, '>='); } } function assertDefined(actual, msg) { if (actual == null) { throwError(msg, actual, null, '!='); } } function throwError(msg, actual, expected, comparison) { throw new Error(`ASSERTION ERROR: ${msg}` + (comparison == null ? '' : ` [Expected=> ${expected} ${comparison} ${actual} <=Actual]`)); } function assertDomNode(node) { if (!(node instanceof Node)) { throwError(`The provided value must be an instance of a DOM Node but got ${stringify(node)}`); } } function assertIndexInRange(arr, index) { assertDefined(arr, 'Array must be defined.'); const maxLen = arr.length; if (index < 0 || index >= maxLen) { throwError(`Index expected to be less than ${maxLen} but got ${index}`); } } function assertOneOf(value, ...validValues) { if (validValues.indexOf(value) !== -1) return true; throwError(`Expected value to be one of ${JSON.stringify(validValues)} but was ${JSON.stringify(value)}.`); } /** * Construct an injectable definition which defines how a token will be constructed by the DI * system, and in which injectors (if any) it will be available. * * This should be assigned to a static `ɵprov` field on a type, which will then be an * `InjectableType`. * * Options: * * `providedIn` determines which injectors will include the injectable, by either associating it * with an `@NgModule` or other `InjectorType`, or by specifying that this injectable should be * provided in the `'root'` injector, which will be the application-level injector in most apps. * * `factory` gives the zero argument function which will create an instance of the injectable. * The factory can call `inject` to access the `Injector` and request injection of dependencies. * * @codeGenApi * @publicApi This instruction has been emitted by ViewEngine for some time and is deployed to npm. */ function ɵɵdefineInjectable(opts) { return { token: opts.token, providedIn: opts.providedIn || null, factory: opts.factory, value: undefined, }; } /** * @deprecated in v8, delete after v10. This API should be used only by generated code, and that * code should now use ɵɵdefineInjectable instead. * @publicApi */ const defineInjectable = exports('defineInjectable', ɵɵdefineInjectable); /** * Construct an `InjectorDef` which configures an injector. * * This should be assigned to a static injector def (`ɵinj`) field on a type, which will then be an * `InjectorType`. * * Options: * * * `providers`: an optional array of providers to add to the injector. Each provider must * either have a factory or point to a type which has a `ɵprov` static property (the * type must be an `InjectableType`). * * `imports`: an optional array of imports of other `InjectorType`s or `InjectorTypeWithModule`s * whose providers will also be added to the injector. Locally provided types will override * providers from imports. * * @codeGenApi */ function ɵɵdefineInjector(options) { return { providers: options.providers || [], imports: options.imports || [] }; } /** * Read the injectable def (`ɵprov`) for `type` in a way which is immune to accidentally reading * inherited value. * * @param type A type which may have its own (non-inherited) `ɵprov`. */ function getInjectableDef(type) { return getOwnDefinition(type, NG_PROV_DEF) || getOwnDefinition(type, NG_INJECTABLE_DEF); } function isInjectable(type) { return getInjectableDef(type) !== null; } /** * Return definition only if it is defined directly on `type` and is not inherited from a base * class of `type`. */ function getOwnDefinition(type, field) { return type.hasOwnProperty(field) ? type[field] : null; } /** * Read the injectable def (`ɵprov`) for `type` or read the `ɵprov` from one of its ancestors. * * @param type A type which may have `ɵprov`, via inheritance. * * @deprecated Will be removed in a future version of Angular, where an error will occur in the * scenario if we find the `ɵprov` on an ancestor only. */ function getInheritedInjectableDef(type) { const def = type && (type[NG_PROV_DEF] || type[NG_INJECTABLE_DEF]); if (def) { ngDevMode && console.warn(`DEPRECATED: DI is instantiating a token "${type.name}" that inherits its @Injectable decorator but does not provide one itself.\n` + `This will become an error in a future version of Angular. Please add @Injectable() to the "${type.name}" class.`); return def; } else { return null; } } /** * Read the injector def type in a way which is immune to accidentally reading inherited value. * * @param type type which may have an injector def (`ɵinj`) */ function getInjectorDef(type) { return type && (type.hasOwnProperty(NG_INJ_DEF) || type.hasOwnProperty(NG_INJECTOR_DEF)) ? type[NG_INJ_DEF] : null; } const NG_PROV_DEF = exports('ɵNG_PROV_DEF', getClosureSafeProperty({ ɵprov: getClosureSafeProperty })); const NG_INJ_DEF = exports('ɵNG_INJ_DEF', getClosureSafeProperty({ ɵinj: getClosureSafeProperty })); // We need to keep these around so we can read off old defs if new defs are unavailable const NG_INJECTABLE_DEF = getClosureSafeProperty({ ngInjectableDef: getClosureSafeProperty }); const NG_INJECTOR_DEF = getClosureSafeProperty({ ngInjectorDef: getClosureSafeProperty }); /** * Injection flags for DI. * * @publicApi * @deprecated use an options object for `inject` instead. */ var InjectFlags; exports('InjectFlags', InjectFlags); (function (InjectFlags) { // TODO(alxhub): make this 'const' (and remove `InternalInjectFlags` enum) when ngc no longer // writes exports of it into ngfactory files. /** Check self and check parent injector if needed */ InjectFlags[InjectFlags["Default"] = 0] = "Default"; /** * Specifies that an injector should retrieve a dependency from any injector until reaching the * host element of the current component. (Only used with Element Injector) */ InjectFlags[InjectFlags["Host"] = 1] = "Host"; /** Don't ascend to ancestors of the node requesting injection. */ InjectFlags[InjectFlags["Self"] = 2] = "Self"; /** Skip the node that is requesting injection. */ InjectFlags[InjectFlags["SkipSelf"] = 4] = "SkipSelf"; /** Inject `defaultValue` instead if token not found. */ InjectFlags[InjectFlags["Optional"] = 8] = "Optional"; })(InjectFlags || (exports('InjectFlags', InjectFlags = {}))); /** * Current implementation of inject. * * By default, it is `injectInjectorOnly`, which makes it `Injector`-only aware. It can be changed * to `directiveInject`, which brings in the `NodeInjector` system of ivy. It is designed this * way for two reasons: * 1. `Injector` should not depend on ivy logic. * 2. To maintain tree shake-ability we don't want to bring in unnecessary code. */ let _injectImplementation; function getInjectImplementation() { return _injectImplementation; } /** * Sets the current inject implementation. */ function setInjectImplementation(impl) { const previous = _injectImplementation; _injectImplementation = impl; return previous; } /** * Injects `root` tokens in limp mode. * * If no injector exists, we can still inject tree-shakable providers which have `providedIn` set to * `"root"`. This is known as the limp mode injection. In such case the value is stored in the * injectable definition. */ function injectRootLimpMode(token, notFoundValue, flags) { const injectableDef = getInjectableDef(token); if (injectableDef && injectableDef.providedIn == 'root') { return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() : injectableDef.value; } if (flags & InjectFlags.Optional) return null; if (notFoundValue !== undefined) return notFoundValue; throwProviderNotFoundError(stringify(token), 'Injector'); } /** * Assert that `_injectImplementation` is not `fn`. * * This is useful, to prevent infinite recursion. * * @param fn Function which it should not equal to */ function assertInjectImplementationNotEqual(fn) { ngDevMode && assertNotEqual(_injectImplementation, fn, 'Calling ɵɵinject would cause infinite recursion'); } // Always use __globalThis if available, which is the spec-defined global variable across all // environments, then fallback to __global first, because in Node tests both __global and // __window may be defined and _global should be __global in that case. Note: Typeof/Instanceof // checks are considered side-effects in Terser. We explicitly mark this as side-effect free: // https://github.com/terser/terser/issues/250. const _global = exports('ɵglobal', ( /* @__PURE__ */(() => (typeof globalThis !== 'undefined' && globalThis) || (typeof global !== 'undefined' && global) || (typeof window !== 'undefined' && window) || (typeof self !== 'undefined' && typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope && self))())); function ngDevModeResetPerfCounters() { const locationString = typeof location !== 'undefined' ? location.toString() : ''; const newCounters = { namedConstructors: locationString.indexOf('ngDevMode=namedConstructors') != -1, firstCreatePass: 0, tNode: 0, tView: 0, rendererCreateTextNode: 0, rendererSetText: 0, rendererCreateElement: 0, rendererAddEventListener: 0, rendererSetAttribute: 0, rendererRemoveAttribute: 0, rendererSetProperty: 0, rendererSetClassName: 0, rendererAddClass: 0, rendererRemoveClass: 0, rendererSetStyle: 0, rendererRemoveStyle: 0, rendererDestroy: 0, rendererDestroyNode: 0, rendererMoveNode: 0, rendererRemoveNode: 0, rendererAppendChild: 0, rendererInsertBefore: 0, rendererCreateComment: 0, hydratedNodes: 0, hydratedComponents: 0, dehydratedViewsRemoved: 0, dehydratedViewsCleanupRuns: 0, componentsSkippedHydration: 0, }; // Make sure to refer to ngDevMode as ['ngDevMode'] for closure. const allowNgDevModeTrue = locationString.indexOf('ngDevMode=false') === -1; _global['ngDevMode'] = allowNgDevModeTrue && newCounters; return newCounters; } /** * This function checks to see if the `ngDevMode` has been set. If yes, * then we honor it, otherwise we default to dev mode with additional checks. * * The idea is that unless we are doing production build where we explicitly * set `ngDevMode == false` we should be helping the developer by providing * as much early warning and errors as possible. * * `ɵɵdefineComponent` is guaranteed to have been called before any component template functions * (and thus Ivy instructions), so a single initialization there is sufficient to ensure ngDevMode * is defined for the entire instruction set. * * When checking `ngDevMode` on toplevel, always init it before referencing it * (e.g. `((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode())`), otherwise you can * get a `ReferenceError` like in https://github.com/angular/angular/issues/31595. * * Details on possible values for `ngDevMode` can be found on its docstring. * * NOTE: * - changes to the `ngDevMode` name must be synced with `compiler-cli/src/tooling.ts`. */ function initNgDevMode() { // The below checks are to ensure that calling `initNgDevMode` multiple times does not // reset the counters. // If the `ngDevMode` is not an object, then it means we have not created the perf counters // yet. if (typeof ngDevMode === 'undefined' || ngDevMode) { if (typeof ngDevMode !== 'object') { ngDevModeResetPerfCounters(); } return typeof ngDevMode !== 'undefined' && !!ngDevMode; } return false; } const _THROW_IF_NOT_FOUND = {}; const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND; /* * Name of a property (that we patch onto DI decorator), which is used as an annotation of which * InjectFlag this decorator represents. This allows to avoid direct references to the DI decorators * in the code, thus making them tree-shakable. */ const DI_DECORATOR_FLAG = '__NG_DI_FLAG__'; const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath'; const NG_TOKEN_PATH = 'ngTokenPath'; const NEW_LINE = /\n/gm; const NO_NEW_LINE = 'ɵ'; const SOURCE = '__source'; /** * Current injector value used by `inject`. * - `undefined`: it is an error to call `inject` * - `null`: `inject` can be called but there is no injector (limp-mode). * - Injector instance: Use the injector for resolution. */ let _currentInjector = undefined; function getCurrentInjector() { return _currentInjector; } function setCurrentInjector(injector) { const former = _currentInjector; _currentInjector = injector; return former; } function injectInjectorOnly(token, flags = InjectFlags.Default) { if (_currentInjector === undefined) { throw new RuntimeError(-203 /* RuntimeErrorCode.MISSING_INJECTION_CONTEXT */, ngDevMode && `inject() must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with \`runInInjectionContext\`.`); } else if (_currentInjector === null) { return injectRootLimpMode(token, undefined, flags); } else { return _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags); } } function ɵɵinject(token, flags = InjectFlags.Default) { return (getInjectImplementation() || injectInjectorOnly)(resolveForwardRef(token), flags); } /** * Throws an error indicating that a factory function could not be generated by the compiler for a * particular class. * * The name of the class is not mentioned here, but will be in the generated factory function name * and thus in the stack trace. * * @codeGenApi */ function ɵɵinvalidFactoryDep(index) { throw new RuntimeError(202 /* RuntimeErrorCode.INVALID_FACTORY_DEPENDENCY */, ngDevMode && `This constructor is not compatible with Angular Dependency Injection because its dependency at index ${index} of the parameter list is invalid. This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator. Please check that 1) the type for the parameter at index ${index} is correct and 2) the correct Angular decorators are defined for this class and its ancestors.`); } /** * Injects a token from the currently active injector. * `inject` is only supported during instantiation of a dependency by the DI system. It can be used * during: * - Construction (via the `constructor`) of a class being instantiated by the DI system, such * as an `@Injectable` or `@Component`. * - In the initializer for fields of such classes. * - In the factory function specified for `useFactory` of a `Provider` or an `@Injectable`. * - In the `factory` function specified for an `InjectionToken`. * * @param token A token that represents a dependency that should be injected. * @param flags Optional flags that control how injection is executed. * The flags correspond to injection strategies that can be specified with * parameter decorators `@Host`, `@Self`, `@SkipSelf`, and `@Optional`. * @returns the injected value if operation is successful, `null` otherwise. * @throws if called outside of a supported context. * * @usageNotes * In practice the `inject()` calls are allowed in a constructor, a constructor parameter and a * field initializer: * * ```typescript * @Injectable({providedIn: 'root'}) * export class Car { * radio: Radio|undefined; * // OK: field initializer * spareTyre = inject(Tyre); * * constructor() { * // OK: constructor body * this.radio = inject(Radio); * } * } * ``` * * It is also legal to call `inject` from a provider's factory: * * ```typescript * providers: [ * {provide: Car, useFactory: () => { * // OK: a class factory * const engine = inject(Engine); * return new Car(engine); * }} * ] * ``` * * Calls to the `inject()` function outside of the class creation context will result in error. Most * notably, calls to `inject()` are disallowed after a class instance was created, in methods * (including lifecycle hooks): * * ```typescript * @Component({ ... }) * export class CarComponent { * ngOnInit() { * // ERROR: too late, the component instance was already created * const engine = inject(Engine); * engine.start(); * } * } * ``` * * @publicApi */ function inject(token, flags = InjectFlags.Default) { return ɵɵinject(token, convertToBitFlags(flags)); } // Converts object-based DI flags (`InjectOptions`) to bit flags (`InjectFlags`). function convertToBitFlags(flags) { if (typeof flags === 'undefined' || typeof flags === 'number') { return flags; } // While TypeScript doesn't accept it without a cast, bitwise OR with false-y values in // JavaScript is a no-op. We can use that for a very codesize-efficient conversion from // `InjectOptions` to `InjectFlags`. return (0 /* InternalInjectFlags.Default */ | // comment to force a line break in the formatter (flags.optional && 8 /* InternalInjectFlags.Optional */) | (flags.host && 1 /* InternalInjectFlags.Host */) | (flags.self && 2 /* InternalInjectFlags.Self */) | (flags.skipSelf && 4 /* InternalInjectFlags.SkipSelf */)); } function injectArgs(types) { const args = []; for (let i = 0; i < types.length; i++) { const arg = resolveForwardRef(types[i]); if (Array.isArray(arg)) { if (arg.length === 0) { throw new RuntimeError(900 /* RuntimeErrorCode.INVALID_DIFFER_INPUT */, ngDevMode && 'Arguments array must have arguments.'); } let type = undefined; let flags = InjectFlags.Default; for (let j = 0; j < arg.length; j++) { const meta = arg[j]; const flag = getInjectFlag(meta); if (typeof flag === 'number') { // Special case when we handle @Inject decorator. if (flag === -1 /* DecoratorFlags.Inject */) { type = meta.token; } else { flags |= flag; } } else { type = meta; } } args.push(ɵɵinject(type, flags)); } else { args.push(ɵɵinject(arg)); } } return args; } /** * Attaches a given InjectFlag to a given decorator using monkey-patching. * Since DI decorators can be used in providers `deps` array (when provider is configured using * `useFactory`) without initialization (e.g. `Host`) and as an instance (e.g. `new Host()`), we * attach the flag to make it available both as a static property and as a field on decorator * instance. * * @param decorator Provided DI decorator. * @param flag InjectFlag that should be applied. */ function attachInjectFlag(decorator, flag) { decorator[DI_DECORATOR_FLAG] = flag; decorator.prototype[DI_DECORATOR_FLAG] = flag; return decorator; } /** * Reads monkey-patched property that contains InjectFlag attached to a decorator. * * @param token Token that may contain monkey-patched DI flags property. */ function getInjectFlag(token) { return token[DI_DECORATOR_FLAG]; } function catchInjectorError(e, token, injectorErrorName, source) { const tokenPath = e[NG_TEMP_TOKEN_PATH]; if (token[SOURCE]) { tokenPath.unshift(token[SOURCE]); } e.message = formatError('\n' + e.message, tokenPath, injectorErrorName, source); e[NG_TOKEN_PATH] = tokenPath; e[NG_TEMP_TOKEN_PATH] = null; throw e; } function formatError(text, obj, injectorErrorName, source = null) { text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.slice(2) : text; let context = stringify(obj); if (Array.isArray(obj)) { context = obj.map(stringify).join(' -> '); } else if (typeof obj === 'object') { let parts = []; for (let key in obj) { if (obj.hasOwnProperty(key)) { let value = obj[key]; parts.push(key + ':' + (typeof value === 'string' ? JSON.stringify(value) : stringify(value))); } } context = `{${parts.join(', ')}}`; } return `${injectorErrorName}${source ? '(' + source + ')' : ''}[${context}]: ${text.replace(NEW_LINE, '\n ')}`; } /** * Convince closure compiler that the wrapped function has no side-effects. * * Closure compiler always assumes that `toString` has no side-effects. We use this quirk to * allow us to execute a function but have closure compiler mark the call as no-side-effects. * It is important that the return value for the `noSideEffects` function be assigned * to something which is retained otherwise the call to `noSideEffects` will be removed by closure * compiler. */ function noSideEffects(fn) { return { toString: fn }.toString(); } /** * The strategy that the default change detector uses to detect changes. * When set, takes effect the next time change detection is triggered. * * @see {@link ChangeDetectorRef#usage-notes Change detection usage} * * @publicApi */ var ChangeDetectionStrategy; exports('ChangeDetectionStrategy', ChangeDetectionStrategy); (function (ChangeDetectionStrategy) { /** * Use the `CheckOnce` strategy, meaning that automatic change detection is deactivated * until reactivated by setting the strategy to `Default` (`CheckAlways`). * Change detection can still be explicitly invoked. * This strategy applies to all child directives and cannot be overridden. */ ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush"; /** * Use the default `CheckAlways` strategy, in which change detection is automatic until * explicitly deactivated. */ ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default"; })(ChangeDetectionStrategy || (exports('ChangeDetectionStrategy', ChangeDetectionStrategy = {}))); /** * Defines the CSS styles encapsulation policies for the {@link Component} decorator's * `encapsulation` option. * * See {@link Component#encapsulation encapsulation}. * * @usageNotes * ### Example * * {@example core/ts/metadata/encapsulation.ts region='longform'} * * @publicApi */ var ViewEncapsulation$1; exports('ViewEncapsulation', ViewEncapsulation$1); (function (ViewEncapsulation) { // TODO: consider making `ViewEncapsulation` a `const enum` instead. See // https://github.com/angular/angular/issues/44119 for additional information. /** * Emulates a native Shadow DOM encapsulation behavior by adding a specific attribute to the * component's host element and applying the same attribute to all the CSS selectors provided * via {@link Component#styles styles} or {@link Component#styleUrls styleUrls}. * * This is the default option. */ ViewEncapsulation[ViewEncapsulation["Emulated"] = 0] = "Emulated"; // Historically the 1 value was for `Native` encapsulation which has been removed as of v11. /** * Doesn't provide any sort of CSS style encapsulation, meaning that all the styles provided * via {@link Component#styles styles} or {@link Component#styleUrls styleUrls} are applicable * to any HTML element of the application regardless of their host Component. */ ViewEncapsulation[ViewEncapsulation["None"] = 2] = "None"; /** * Uses the browser's native Shadow DOM API to encapsulate CSS styles, meaning that it creates * a ShadowRoot for the component's host element which is then used to encapsulate * all the Component's styling. */ ViewEncapsulation[ViewEncapsulation["ShadowDom"] = 3] = "ShadowDom"; })(ViewEncapsulation$1 || (exports('ViewEncapsulation', ViewEncapsulation$1 = {}))); /** * This file contains reuseable "empty" symbols that can be used as default return values * in different parts of the rendering code. Because the same symbols are returned, this * allows for identity checks against these values to be consistently used by the framework * code. */ const EMPTY_OBJ = {}; const EMPTY_ARRAY = []; // freezing the values prevents any code from accidentally inserting new values in if ((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode()) { // These property accesses can be ignored because ngDevMode will be set to false // when optimizing code and the whole if statement will be dropped. // tslint:disable-next-line:no-toplevel-property-access Object.freeze(EMPTY_OBJ); // tslint:disable-next-line:no-toplevel-property-access Object.freeze(EMPTY_ARRAY); } const NG_COMP_DEF = exports('ɵNG_COMP_DEF', getClosureSafeProperty({ ɵcmp: getClosureSafeProperty })); const NG_DIR_DEF = exports('ɵNG_DIR_DEF', getClosureSafeProperty({ ɵdir: getClosureSafeProperty })); const NG_PIPE_DEF = exports('ɵNG_PIPE_DEF', getClosureSafeProperty({ ɵpipe: getClosureSafeProperty })); const NG_MOD_DEF = exports('ɵNG_MOD_DEF', getClosureSafeProperty({ ɵmod: getClosureSafeProperty })); const NG_FACTORY_DEF = getClosureSafeProperty({ ɵfac: getClosureSafeProperty }); /** * If a directive is diPublic, bloomAdd sets a property on the type with this constant as * the key and the directive's unique ID as the value. This allows us to map directives to their * bloom filter bit for DI. */ // TODO(misko): This is wrong. The NG_ELEMENT_ID should never be minified. const NG_ELEMENT_ID = exports('ɵNG_ELEMENT_ID', getClosureSafeProperty({ __NG_ELEMENT_ID__: getClosureSafeProperty })); /** * The `NG_ENV_ID` field on a DI token indicates special processing in the `EnvironmentInjector`: * getting such tokens from the `EnvironmentInjector` will bypass the standard DI resolution * strategy and instead will return implementation produced by the `NG_ENV_ID` factory function. * * This particular retrieval of DI tokens is mostly done to eliminate circular dependencies and * improve tree-shaking. */ const NG_ENV_ID = getClosureSafeProperty({ __NG_ENV_ID__: getClosureSafeProperty }); /** * Returns an index of `classToSearch` in `className` taking token boundaries into account. * * `classIndexOf('AB A', 'A', 0)` will be 3 (not 0 since `AB!==A`) * * @param className A string containing classes (whitespace separated) * @param classToSearch A class name to locate * @param startingIndex Starting location of search * @returns an index of the located class (or -1 if not found) */ function classIndexOf(className, classToSearch, startingIndex) { ngDevMode && assertNotEqual(classToSearch, '', 'can not look for "" string.'); let end = className.length; while (true) { const foundIndex = className.indexOf(classToSearch, startingIndex); if (foundIndex === -1) return foundIndex; if (foundIndex === 0 || className.charCodeAt(foundIndex - 1) <= 32 /* CharCode.SPACE */) { // Ensure that it has leading whitespace const length = classToSearch.length; if (foundIndex + length === end || className.charCodeAt(foundIndex + length) <= 32 /* CharCode.SPACE */) { // Ensure that it has trailing whitespace return foundIndex; } } // False positive, keep searching from where we left off. startingIndex = foundIndex + 1; } } /** * Assigns all attribute values to the provided element via the inferred renderer. * * This function accepts two forms of attribute entries: * * default: (key, value): * attrs = [key1, value1, key2, value2] * * namespaced: (NAMESPACE_MARKER, uri, name, value) * attrs = [NAMESPACE_MARKER, uri, name, value, NAMESPACE_MARKER, uri, name, value] * * The `attrs` array can contain a mix of both the default and namespaced entries. * The "default" values are set without a marker, but if the function comes across * a marker value then it will attempt to set a namespaced value. If the marker is * not of a namespaced value then the function will quit and return the index value * where it stopped during the iteration of the attrs array. * * See [AttributeMarker] to understand what the namespace marker value is. * * Note that this instruction does not support assigning style and class values to * an element. See `elementStart` and `elementHostAttrs` to learn how styling values * are applied to an element. * @param renderer The renderer to be used * @param native The element that the attributes will be assigned to * @param attrs The attribute array of values that will be assigned to the element * @returns the index value that was last accessed in the attributes array */ function setUpAttributes(renderer, native, attrs) { let i = 0; while (i < attrs.length) { const value = attrs[i]; if (typeof value === 'number') { // only namespaces are supported. Other value types (such as style/class // entries) are not supported in this function. if (value !== 0 /* AttributeMarker.NamespaceURI */) { break; } // we just landed on the marker value ... therefore // we should skip to the next entry i++; const namespaceURI = attrs[i++]; const attrName = attrs[i++]; const attrVal = attrs[i++]; ngDevMode && ngDevMode.rendererSetAttribute++; renderer.setAttribute(native, attrName, attrVal, namespaceURI); } else { // attrName is string; const attrName = value; const attrVal = attrs[++i]; // Standard attributes ngDevMode && ngDevMode.rendererSetAttribute++; if (isAnimationProp(attrName)) { renderer.setProperty(native, attrName, attrVal); } else { renderer.setAttribute(native, attrName, attrVal); } i++; } } // another piece of code may iterate over the same attributes array. Therefore // it may be helpful to return the exact spot where the attributes array exited // whether by running into an unsupported marker or if all the static values were // iterated over. return i; } /** * Test whether the given value is a marker that indicates that the following * attribute values in a `TAttributes` array are only the names of attributes, * and not name-value pairs. * @param marker The attribute marker to test. * @returns true if the marker is a "name-only" marker (e.g. `Bindings`, `Template` or `I18n`). */ function isNameOnlyAttributeMarker(marker) { return marker === 3 /* AttributeMarker.Bindings */ || marker === 4 /* AttributeMarker.Template */ || marker === 6 /* AttributeMarker.I18n */; } function isAnimationProp(name) { // Perf note: accessing charCodeAt to check for the first character of a string is faster as // compared to accessing a character at index 0 (ex. name[0]). The main reason for this is that // charCodeAt doesn't allocate memory to return a substring. return name.charCodeAt(0) === 64 /* CharCode.AT_SIGN */; } /** * Merges `src` `TAttributes` into `dst` `TAttributes` removing any duplicates in the process. * * This merge function keeps the order of attrs same. * * @param dst Location of where the merged `TAttributes` should end up. * @param src `TAttributes` which should be appended to `dst` */ function mergeHostAttrs(dst, src) { if (src === null || src.length === 0) ; else if (dst === null || dst.length === 0) { // We have source, but dst is empty, just make a copy. dst = src.slice(); } else { let srcMarker = -1 /* AttributeMarker.ImplicitAttributes */; for (let i = 0; i < src.length; i++) { const item = src[i]; if (typeof item === 'number') { srcMarker = item; } else { if (srcMarker === 0 /* AttributeMarker.NamespaceURI */) ; else if (srcMarker === -1 /* AttributeMarker.ImplicitAttributes */ || srcMarker === 2 /* AttributeMarker.Styles */) { // Case where we have to consume `key1` and `value` only. mergeHostAttribute(dst, srcMarker, item, null, src[++i]); } else { // Case where we have to consume `key1` only. mergeHostAttribute(dst, srcMarker, item, null, null); } } } } return dst; } /** * Append `key`/`value` to existing `TAttributes` taking region marker and duplicates into account. * * @param dst `TAttributes` to append to. * @param marker Region where the `key`/`value` should be added. * @param key1 Key to add to `TAttributes` * @param key2 Key to add to `TAttributes` (in case of `AttributeMarker.NamespaceURI`) * @param value Value to add or to overwrite to `TAttributes` Only used if `marker` is not Class. */ function mergeHostAttribute(dst, marker, key1, key2, value) { let i = 0; // Assume that new markers will be inserted at the end. let markerInsertPosition = dst.length; // scan until correct type. if (marker === -1 /* AttributeMarker.ImplicitAttributes */) { markerInsertPosition = -1; } else { while (i < dst.length) { const dstValue = dst[i++]; if (typeof dstValue === 'number') { if (dstValue === marker) { markerInsertPosition = -1; break; } else if (dstValue > marker) { // We need to save this as we want the markers to be inserted in specific order. markerInsertPosition = i - 1; break; } } } } // search until you find place of insertion while (i < dst.length) { const item = dst[i]; if (typeof item === 'number') { // since `i` started as the index after the marker, we did not find it if we are at the next // marker break; } else if (item === key1) { // We already have same token if (key2 === null) { if (value !== null) { dst[i + 1] = value; } return; } else if (key2 === dst[i + 1]) { dst[i + 2] = value; return; } } // Increment counter. i++; if (key2 !== null) i++; if (value !== null) i++; } // insert at location. if (markerInsertPosition !== -1) { dst.splice(markerInsertPosition, 0, marker); i = markerInsertPosition + 1; } dst.splice(i++, 0, key1); if (key2 !== null) { dst.splice(i++, 0, key2); } if (value !== null) { dst.splice(i++, 0, value); } } const NG_TEMPLATE_SELECTOR = 'ng-template'; /** * Search the `TAttributes` to see if it contains `cssClassToMatch` (case insensitive) * * @param attrs `TAttributes` to search through. * @param cssClassToMatch class to match (lowercase) * @param isProjectionMode Whether or not class matching should look into the attribute `class` in * addition to the `AttributeMarker.Classes`. */ function isCssClassMatching(attrs, cssClassToMatch, isProjectionMode) { // TODO(misko): The fact that this function needs to know about `isProjectionMode` seems suspect. // It is strange to me that sometimes the class information comes in form of `class` attribute // and sometimes in form of `AttributeMarker.Classes`. Some investigation is needed to determine // if that is the right behavior. ngDevMode && assertEqual(cssClassToMatch, cssClassToMatch.toLowerCase(), 'Class name expected to be lowercase.'); let i = 0; // Indicates whether we are processing value from the implicit // attribute section (i.e. before the first marker in the array). let isImplicitAttrsSection = true; while (i < attrs.length) { let item = attrs[i++]; if (typeof item === 'string' && isImplicitAttrsSection) { const value = attrs[i++]; if (isProjectionMode && item === 'class') { // We found a `class` attribute in the implicit attribute section, // check if it matches the value of the `cssClassToMatch` argument. if (classIndexOf(value.toLowerCase(), cssClassToMatch, 0) !== -1) { return true; } } } else if (item === 1 /* AttributeMarker.Classes */) { // We found the classes section. Start searching for the class. while (i < attrs.length && typeof (item = attrs[i++]) == 'string') { // while we have strings if (item.toLowerCase() === cssClassToMatch) return true; } return false; } else if (typeof item === 'number') { // We've came across a first marker, which indicates // that the implicit attribute section is over. isImplicitAttrsSection = false; } } return false; } /** * Checks whether the `tNode` represents an inline template (e.g. `*ngFor`). * * @param tNode current TNode */ function isInlineTemplate(tNode) { return tNode.type === 4 /* TNodeType.Container */ && tNode.value !== NG_TEMPLATE_SELECTOR; } /** * Function that checks whether a given tNode matches tag-based selector and has a valid type. * * Matching can be performed in 2 modes: projection mode (when we project nodes) and regular * directive matching mode: * - in the "directive matching" mode we do _not_ take TContainer's tagName into account if it is * different from NG_TEMPLATE_SELECTOR (value different from NG_TEMPLATE_SELECTOR indicates that a * tag name was extracted from * syntax so we would match the same directive twice); * - in the "projection" mode, we use a tag name potentially extracted from the * syntax processing * (applicable to TNodeType.Container only). */ function hasTagAndTypeMatch(tNode, currentSelector, isProjectionMode) { const tagNameToCompare = tNode.type === 4 /* TNodeType.Container */ && !isProjectionMode ? NG_TEMPLATE_SELECTOR : tNode.value; return currentSelector === tagNameToCompare; } /** * A utility function to match an Ivy node static data against a simple CSS selector * * @param node static data of the node to match * @param selector The selector to try matching against the node. * @param isProjectionMode if `true` we are matching for content projection, otherwise we are doing * directive matching. * @returns true if node matches the selector. */ function isNodeMatchingSelector(tNode, selector, isProjectionMode) { ngDevMode && assertDefined(selector[0], 'Selector should have a tag name'); let mode = 4 /* SelectorFlags.ELEMENT */; const nodeAttrs = tNode.attrs || []; // Find the index of first attribute that has no value, only a name. const nameOnlyMarkerIdx = getNameOnlyMarkerIndex(nodeAttrs); // When processing ":not" selectors, we skip to the next ":not" if the // current one doesn't match let skipToNextSelector = false; for (let i = 0; i < selector.length; i++) { const current = selector[i]; if (typeof current === 'number') { // If we finish processing a :not selector and it hasn't failed, return false if (!skipToNextSelector && !isPositive(mode) && !isPositive(current)) { return false; } // If we are skipping to the next :not() and this mode flag is positive, // it's a part of the current :not() selector, and we should keep skipping if (skipToNextSelector && isPositive(current)) continue; skipToNextSelector = false; mode = current | (mode & 1 /* SelectorFlags.NOT */); continue; } if (skipToNextSelector) continue; if (mode & 4 /* SelectorFlags.ELEMENT */) { mode = 2 /* SelectorFlags.ATTRIBUTE */ | mode & 1 /* SelectorFlags.NOT */; if (current !== '' && !hasTagAndTypeMatch(tNode, current, isProjectionMode) || current === '' && selector.length === 1) { if (isPositive(mode)) return false; skipToNextSelector = true; } } else { const selectorAttrValue = mode & 8 /* SelectorFlags.CLASS */ ? current : selector[++i]; // special case for matching against classes when a tNode has been instantiated with // class and style values as separate attribute values (e.g. ['title', CLASS, 'foo']) if ((mode & 8 /* SelectorFlags.CLASS */) && tNode.attrs !== null) { if (!isCssClassMatching(tNode.attrs, selectorAttrValue, isProjectionMode)) { if (isPositive(mode)) return false; skipToNextSelector = true; } continue; } const attrName = (mode & 8 /* SelectorFlags.CLASS */) ? 'class' : current; const attrIndexInNode = findAttrIndexInNode(attrName, nodeAttrs, isInlineTemplate(tNode), isProjectionMode); if (attrIndexInNode === -1) { if (isPositive(mode)) return false; skipToNextSelector = true; continue; } if (selectorAttrValue !== '') { let nodeAttrValue; if (attrIndexInNode > nameOnlyMarkerIdx) { nodeAttrValue = ''; } else { ngDevMode && assertNotEqual(nodeAttrs[attrIndexInNode], 0 /* AttributeMarker.NamespaceURI */, 'We do not match directives on namespaced attributes'); // we lowercase the attribute value to be able to match // selectors without case-sensitivity // (selectors are already in lowercase when generated) nodeAttrValue = nodeAttrs[attrIndexInNode + 1].toLowerCase(); } const compareAgainstClassName = mode & 8 /* SelectorFlags.CLASS */ ? nodeAttrValue : null; if (compareAgainstClassName && classIndexOf(compareAgainstClassName, selectorAttrValue, 0) !== -1 || mode & 2 /* SelectorFlags.ATTRIBUTE */ && selectorAttrValue !== nodeAttrValue) { if (isPositive(mode)) return false; skipToNextSelector = true; } } } } return isPositive(mode) || skipToNextSelector; } function isPositive(mode) { return (mode & 1 /* SelectorFlags.NOT */) === 0; } /** * Examines the attribute's definition array for a node to find the index of the * attribute that matches the given `name`. * * NOTE: This will not match namespaced attributes. * * Attribute matching depends upon `isInlineTemplate` and `isProjectionMode`. * The following table summarizes which types of attributes we attempt to match: * * =========================================================================================================== * Modes | Normal Attributes | Bindings Attributes | Template Attributes | I18n * Attributes * =========================================================================================================== * Inline + Projection | YES | YES | NO | YES * ----------------------------------------------------------------------------------------------------------- * Inline + Directive | NO | NO | YES | NO * ----------------------------------------------------------------------------------------------------------- * Non-inline + Projection | YES | YES | NO | YES * ----------------------------------------------------------------------------------------------------------- * Non-inline + Directive | YES | YES | NO | YES * =========================================================================================================== * * @param name the name of the attribute to find * @param attrs the attribute array to examine * @param isInlineTemplate true if the node being matched is an inline template (e.g. `*ngFor`) * rather than a manually expanded template node (e.g ``). * @param isProjectionMode true if we are matching against content projection otherwise we are * matching against directives. */ function findAttrIndexInNode(name, attrs, isInlineTemplate, isProjectionMode) { if (attrs === null) return -1; let i = 0; if (isProjectionMode || !isInlineTemplate) { let bindingsMode = false; while (i < attrs.length) { const maybeAttrName = attrs[i]; if (maybeAttrName === name) { return i; } else if (maybeAttrName === 3 /* AttributeMarker.Bindings */ || maybeAttrName === 6 /* AttributeMarker.I18n */) { bindingsMode = true; } else if (maybeAttrName === 1 /* AttributeMarker.Classes */ || maybeAttrName === 2 /* AttributeMarker.Styles */) { let value = attrs[++i]; // We should skip classes here because we have a separate mechanism for // matching classes in projection mode. while (typeof value === 'string') { value = attrs[++i]; } continue; } else if (maybeAttrName === 4 /* AttributeMarker.Template */) { // We do not care about Template attributes in this scenario. break; } else if (maybeAttrName === 0 /* AttributeMarker.NamespaceURI */) { // Skip the whole namespaced attribute and value. This is by design. i += 4; continue; } // In binding mode there are only names, rather than name-value pairs. i += bindingsMode ? 1 : 2; } // We did not match the attribute return -1; } else { return matchTemplateAttribute(attrs, name); } } function isNodeMatchingSelectorList(tNode, selector, isProjectionMode = false) { for (let i = 0; i < selector.length; i++) { if (isNodeMatchingSelector(tNode, selector[i], isProjectionMode)) { return true; } } return false; } function getProjectAsAttrValue(tNode) { const nodeAttrs = tNode.attrs; if (nodeAttrs != null) { const ngProjectAsAttrIdx = nodeAttrs.indexOf(5 /* AttributeMarker.ProjectAs */); // only check for ngProjectAs in attribute names, don't accidentally match attribute's value // (attribute names are stored at even indexes) if ((ngProjectAsAttrIdx & 1) === 0) { return nodeAttrs[ngProjectAsAttrIdx + 1]; } } return null; } function getNameOnlyMarkerIndex(nodeAttrs) { for (let i = 0; i < nodeAttrs.length; i++) { const nodeAttr = nodeAttrs[i]; if (isNameOnlyAttributeMarker(nodeAttr)) { return i; } } return nodeAttrs.length; } function matchTemplateAttribute(attrs, name) { let i = attrs.indexOf(4 /* AttributeMarker.Template */); if (i > -1) { i++; while (i < attrs.length) { const attr = attrs[i]; // Return in case we checked all template attrs and are switching to the next section in the // attrs array (that starts with a number that represents an attribute marker). if (typeof attr === 'number') return -1; if (attr === name) return i; i++; } } return -1; } /** * Checks whether a selector is inside a CssSelectorList * @param selector Selector to be checked. * @param list List in which to look for the selector. */ function isSelectorInSelectorList(selector, list) { selectorListLoop: for (let i = 0; i < list.length; i++) { const currentSelectorInList = list[i]; if (selector.length !== currentSelectorInList.length) { continue; } for (let j = 0; j < selector.length; j++) { if (selector[j] !== currentSelectorInList[j]) { continue selectorListLoop; } } return true; } return false; } function maybeWrapInNotSelector(isNegativeMode, chunk) { return isNegativeMode ? ':not(' + chunk.trim() + ')' : chunk; } function stringifyCSSSelector(selector) { let result = selector[0]; let i = 1; let mode = 2 /* SelectorFlags.ATTRIBUTE */; let currentChunk = ''; let isNegativeMode = false; while (i < selector.length) { let valueOrMarker = selector[i]; if (typeof valueOrMarker === 'string') { if (mode & 2 /* SelectorFlags.ATTRIBUTE */) { const attrValue = selector[++i]; currentChunk += '[' + valueOrMarker + (attrValue.length > 0 ? '="' + attrValue + '"' : '') + ']'; } else if (mode & 8 /* SelectorFlags.CLASS */) { currentChunk += '.' + valueOrMarker; } else if (mode & 4 /* SelectorFlags.ELEMENT */) { currentChunk += ' ' + valueOrMarker; } } else { // // Append current chunk to the final result in case we come across SelectorFlag, which // indicates that the previous section of a selector is over. We need to accumulate content // between flags to make sure we wrap the chunk later in :not() selector if needed, e.g. // ``` // ['', Flags.CLASS, '.classA', Flags.CLASS | Flags.NOT, '.classB', '.classC'] // ``` // should be transformed to `.classA :not(.classB .classC)`. // // Note: for negative selector part, we accumulate content between flags until we find the // next negative flag. This is needed to support a case where `:not()` rule contains more than // one chunk, e.g. the following selector: // ``` // ['', Flags.ELEMENT | Flags.NOT, 'p', Flags.CLASS, 'foo', Flags.CLASS | Flags.NOT, 'bar'] // ``` // should be stringified to `:not(p.foo) :not(.bar)` // if (currentChunk !== '' && !isPositive(valueOrMarker)) { result += maybeWrapInNotSelector(isNegativeMode, currentChunk); currentChunk = ''; } mode = valueOrMarker; // According to CssSelector spec, once we come across `SelectorFlags.NOT` flag, the negative // mode is maintained for remaining chunks of a selector. isNegativeMode = isNegativeMode || !isPositive(mode); } i++; } if (currentChunk !== '') { result += maybeWrapInNotSelector(isNegativeMode, currentChunk); } return result; } /** * Generates string representation of CSS selector in parsed form. * * ComponentDef and DirectiveDef are generated with the selector in parsed form to avoid doing * additional parsing at runtime (for example, for directive matching). However in some cases (for * example, while bootstrapping a component), a string version of the selector is required to query * for the host element on the page. This function takes the parsed form of a selector and returns * its string representation. * * @param selectorList selector in parsed form * @returns string representation of a given selector */ function stringifyCSSSelectorList(selectorList) { return selectorList.map(stringifyCSSSelector).join(','); } /** * Extracts attributes and classes information from a given CSS selector. * * This function is used while creating a component dynamically. In this case, the host element * (that is created dynamically) should contain attributes and classes specified in component's CSS * selector. * * @param selector CSS selector in parsed form (in a form of array) * @returns object with `attrs` and `classes` fields that contain extracted information */ function extractAttrsAndClassesFromSelector(selector) { const attrs = []; const classes = []; let i = 1; let mode = 2 /* SelectorFlags.ATTRIBUTE */; while (i < selector.length) { let valueOrMarker = selector[i]; if (typeof valueOrMarker === 'string') { if (mode === 2 /* SelectorFlags.ATTRIBUTE */) { if (valueOrMarker !== '') { attrs.push(valueOrMarker, selector[++i]); } } else if (mode === 8 /* SelectorFlags.CLASS */) { classes.push(valueOrMarker); } } else { // According to CssSelector spec, once we come across `SelectorFlags.NOT` flag, the negative // mode is maintained for remaining chunks of a selector. Since attributes and classes are // extracted only for "positive" part of the selector, we can stop here. if (!isPositive(mode)) break; mode = valueOrMarker; } i++; } return { attrs, classes }; } /** * Create a component definition object. * * * # Example * ``` * class MyComponent { * // Generated by Angular Template Compiler * // [Symbol] syntax will not be supported by TypeScript until v2.7 * static ɵcmp = defineComponent({ * ... * }); * } * ``` * @codeGenApi */ function ɵɵdefineComponent(componentDefinition) { 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 baseDef = getNgDirectiveDef(componentDefinition); const def = { ...baseDef, decls: componentDefinition.decls, vars: componentDefinition.vars, template: componentDefinition.template, consts: componentDefinition.consts || null, ngContentSelectors: componentDefinition.ngContentSelectors, onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush, directiveDefs: null, pipeDefs: null, dependencies: baseDef.standalone && componentDefinition.dependencies || null, getStandaloneInjector: null, data: componentDefinition.data || {}, encapsulation: componentDefinition.encapsulation || ViewEncapsulation$1.Emulated, styles: componentDefinition.styles || EMPTY_ARRAY, _: null, schemas: componentDefinition.schemas || null, tView: null, id: '', }; initFeatures(def); const dependencies = componentDefinition.dependencies; def.directiveDefs = extractDefListOrFactory(dependencies, /* pipeDef */ false); def.pipeDefs = extractDefListOrFactory(dependencies, /* pipeDef */ true); def.id = getComponentId(def); return def; }); } /** * Generated next to NgModules to monkey-patch directive and pipe references onto a component's * definition, when generating a direct reference in the component file would otherwise create an * import cycle. * * See [this explanation](https://hackmd.io/Odw80D0pR6yfsOjg_7XCJg?view) for more details. * * @codeGenApi */ function ɵɵsetComponentScope(type, directives, pipes) { const def = type.ɵcmp; def.directiveDefs = extractDefListOrFactory(directives, /* pipeDef */ false); def.pipeDefs = extractDefListOrFactory(pipes, /* pipeDef */ true); } function extractDirectiveDef(type) { return getComponentDef(type) || getDirectiveDef(type); } function nonNull(value) { return value !== null; } /** * @codeGenApi */ function ɵɵdefineNgModule(def) { return noSideEffects(() => { const res = { 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, }; return res; }); } /** * 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 */ function ɵɵsetNgModuleScope(type, scope) { 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; }); } /** * 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, secondary) { if (obj == null) return EMPTY_OBJ; const newLookup = {}; for (const minifiedKey in obj) { if (obj.hasOwnProperty(minifiedKey)) { let publicName = obj[minifiedKey]; let declaredName = publicName; if (Array.isArray(publicName)) { declaredName = publicName[1]; publicName = publicName[0]; } newLookup[publicName] = minifiedKey; if (secondary) { (secondary[publicName] = declaredName); } } } 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 */ function ɵɵdefineDirective(directiveDefinition) { return noSideEffects(() => { const def = getNgDirectiveDef(directiveDefinition); initFeatures(def); return def; }); } /** * 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 */ function ɵɵdefinePipe(pipeDef) { return { type: pipeDef.type, name: pipeDef.name, factory: null, pure: pipeDef.pure !== false, standalone: pipeDef.standalone === true, onDestroy: pipeDef.type.prototype.ngOnDestroy || null }; } /** * The following getter methods retrieve the definition from 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. */ function getComponentDef(type) { return type[NG_COMP_DEF] || null; } function getDirectiveDef(type) { return type[NG_DIR_DEF] || null; } function getPipeDef$1(type) { return type[NG_PIPE_DEF] || null; } /** * Checks whether a given Component, Directive or Pipe is marked as standalone. * This will return false if passed anything other than a Component, Directive, or Pipe class * See [this guide](/guide/standalone-components) for additional information: * * @param type A reference to a Component, Directive or Pipe. * @publicApi */ function isStandalone(type) { const def = getComponentDef(type) || getDirectiveDef(type) || getPipeDef$1(type); return def !== null ? def.standalone : false; } function getNgModuleDef(type, throwNotFound) { const ngModuleDef = type[NG_MOD_DEF] || null; if (!ngModuleDef && throwNotFound === true) { throw new Error(`Type ${stringify(type)} does not have 'ɵmod' property.`); } return ngModuleDef; } function getNgDirectiveDef(directiveDefinition) { const declaredInputs = {}; return { type: directiveDefinition.type, providersResolver: null, factory: null, hostBindings: directiveDefinition.hostBindings || null, hostVars: directiveDefinition.hostVars || 0, hostAttrs: directiveDefinition.hostAttrs || null, contentQueries: directiveDefinition.contentQueries || null, declaredInputs, exportAs: directiveDefinition.exportAs || null, standalone: directiveDefinition.standalone === true, selectors: directiveDefinition.selectors || EMPTY_ARRAY, viewQuery: directiveDefinition.viewQuery || null, features: directiveDefinition.features || null, setInput: null, findHostDirectiveDefs: null, hostDirectives: null, inputs: invertObject(directiveDefinition.inputs, declaredInputs), outputs: invertObject(directiveDefinition.outputs), }; } function initFeatures(definition) { definition.features?.forEach((fn) => fn(definition)); } function extractDefListOrFactory(dependencies, pipeDef) { if (!dependencies) { return null; } const defExtractor = pipeDef ? getPipeDef$1 : extractDirectiveDef; return () => (typeof dependencies === 'function' ? dependencies() : dependencies) .map(dep => defExtractor(dep)) .filter(nonNull); } /** * A map that contains the generated component IDs and type. */ const GENERATED_COMP_IDS = new Map(); /** * A method can returns a component ID from the component definition using a variant of DJB2 hash * algorithm. */ function getComponentId(componentDef) { let hash = 0; // We cannot rely solely on the component selector as the same selector can be used in different // modules. // // `componentDef.style` is not used, due to it causing inconsistencies. Ex: when server // component styles has no sourcemaps and browsers do. // // Example: // https://github.com/angular/components/blob/d9f82c8f95309e77a6d82fd574c65871e91354c2/src/material/core/option/option.ts#L248 // https://github.com/angular/components/blob/285f46dc2b4c5b127d356cb7c4714b221f03ce50/src/material/legacy-core/option/option.ts#L32 const hashSelectors = [ componentDef.selectors, componentDef.ngContentSelectors, componentDef.hostVars, componentDef.hostAttrs, componentDef.consts, componentDef.vars, componentDef.decls, componentDef.encapsulation, componentDef.standalone, componentDef.exportAs, JSON.stringify(componentDef.inputs), JSON.stringify(componentDef.outputs), // We cannot use 'componentDef.type.name' as the name of the symbol will change and will not // match in the server and browser bundles. Object.getOwnPropertyNames(componentDef.type.prototype), !!componentDef.contentQueries, !!componentDef.viewQuery, ].join('|'); for (const char of hashSelectors) { hash = Math.imul(31, hash) + char.charCodeAt(0) << 0; } // Force positive number hash. // 2147483647 = equivalent of Integer.MAX_VALUE. hash += 2147483647 + 1; const compId = 'c' + hash; if (typeof ngDevMode === 'undefined' || ngDevMode) { if (GENERATED_COMP_IDS.has(compId)) { const previousCompDefType = GENERATED_COMP_IDS.get(compId); if (previousCompDefType !== componentDef.type) { console.warn(formatRuntimeError(-912 /* RuntimeErrorCode.COMPONENT_ID_COLLISION */, `Component ID generation collision detected. Components '${previousCompDefType.name}' and '${componentDef.type.name}' with selector '${stringifyCSSSelectorList(componentDef .selectors)}' generated the same component ID. To fix this, you can change the selector of one of those components or add an extra host attribute to force a different ID.`)); } } else { GENERATED_COMP_IDS.set(compId, componentDef.type); } } return compId; } // Below are constants for LView indices to help us look up LView members // without having to remember the specific indices. // Uglify will inline these when minifying so there shouldn't be a cost. const HOST = 0; const TVIEW = 1; const FLAGS = 2; const PARENT = 3; const NEXT = 4; const DESCENDANT_VIEWS_TO_REFRESH = 5; const T_HOST = 6; const CLEANUP = 7; const CONTEXT = 8; const INJECTOR$1 = 9; const ENVIRONMENT = 10; const RENDERER = 11; const CHILD_HEAD = 12; const CHILD_TAIL = 13; // FIXME(misko): Investigate if the three declarations aren't all same thing. const DECLARATION_VIEW = 14; const DECLARATION_COMPONENT_VIEW = 15; const DECLARATION_LCONTAINER = 16; const PREORDER_HOOK_FLAGS = 17; const QUERIES = 18; const ID = 19; const EMBEDDED_VIEW_INJECTOR = 20; const ON_DESTROY_HOOKS = 21; const HYDRATION = 22; const REACTIVE_TEMPLATE_CONSUMER = 23; const REACTIVE_HOST_BINDING_CONSUMER = 24; /** * Size of LView's header. Necessary to adjust for it when setting slots. * * IMPORTANT: `HEADER_OFFSET` should only be referred to the in the `ɵɵ*` instructions to translate * instruction index into `LView` index. All other indexes should be in the `LView` index space and * there should be no need to refer to `HEADER_OFFSET` anywhere else. */ const HEADER_OFFSET = 25; /** * Special location which allows easy identification of type. If we have an array which was * retrieved from the `LView` and that array has `true` at `TYPE` location, we know it is * `LContainer`. */ const TYPE = 1; /** * Below are constants for LContainer indices to help us look up LContainer members * without having to remember the specific indices. * Uglify will inline these when minifying so there shouldn't be a cost. */ /** * Flag to signify that this `LContainer` may have transplanted views which need to be change * detected. (see: `LView[DECLARATION_COMPONENT_VIEW])`. * * This flag, once set, is never unset for the `LContainer`. This means that when unset we can skip * a lot of work in `refreshEmbeddedViews`. But when set we still need to verify * that the `MOVED_VIEWS` are transplanted and on-push. */ const HAS_TRANSPLANTED_VIEWS = 2; // PARENT, NEXT, DESCENDANT_VIEWS_TO_REFRESH are indices 3, 4, and 5 // As we already have these constants in LView, we don't need to re-create them. // T_HOST is index 6 // We already have this constants in LView, we don't need to re-create it. const NATIVE = 7; const VIEW_REFS = 8; const MOVED_VIEWS = 9; const DEHYDRATED_VIEWS = 10; /** * Size of LContainer's header. Represents the index after which all views in the * container will be inserted. We need to keep a record of current views so we know * which views are already in the DOM (and don't need to be re-added) and so we can * remove views from the DOM when they are no longer required. */ const CONTAINER_HEADER_OFFSET = 11; /** * True if `value` is `LView`. * @param value wrapped value of `RNode`, `LView`, `LContainer` */ function isLView(value) { return Array.isArray(value) && typeof value[TYPE] === 'object'; } /** * True if `value` is `LContainer`. * @param value wrapped value of `RNode`, `LView`, `LContainer` */ function isLContainer(value) { return Array.isArray(value) && value[TYPE] === true; } function isContentQueryHost(tNode) { return (tNode.flags & 4 /* TNodeFlags.hasContentQuery */) !== 0; } function isComponentHost(tNode) { return tNode.componentOffset > -1; } function isDirectiveHost(tNode) { return (tNode.flags & 1 /* TNodeFlags.isDirectiveHost */) === 1 /* TNodeFlags.isDirectiveHost */; } function isComponentDef(def) { return !!def.template; } function isRootView(target) { return (target[FLAGS] & 512 /* LViewFlags.IsRoot */) !== 0; } function isProjectionTNode(tNode) { return (tNode.type & 16 /* TNodeType.Projection */) === 16 /* TNodeType.Projection */; } // [Assert functions do not constraint type when they are guarded by a truthy // expression.](https://github.com/microsoft/TypeScript/issues/37295) function assertTNodeForLView(tNode, lView) { assertTNodeForTView(tNode, lView[TVIEW]); } function assertTNodeForTView(tNode, tView) { assertTNode(tNode); tNode.hasOwnProperty('tView_') && assertEqual(tNode.tView_, tView, 'This TNode does not belong to this TView.'); } function assertTNode(tNode) { assertDefined(tNode, 'TNode must be defined'); if (!(tNode && typeof tNode === 'object' && tNode.hasOwnProperty('directiveStylingLast'))) { throwError('Not of type TNode, got: ' + tNode); } } function assertTIcu(tIcu) { assertDefined(tIcu, 'Expected TIcu to be defined'); if (!(typeof tIcu.currentCaseLViewIndex === 'number')) { throwError('Object is not of TIcu type.'); } } function assertComponentType(actual, msg = 'Type passed in is not ComponentType, it does not have \'ɵcmp\' property.') { if (!getComponentDef(actual)) { throwError(msg); } } function assertNgModuleType(actual, msg = 'Type passed in is not NgModuleType, it does not have \'ɵmod\' property.') { if (!getNgModuleDef(actual)) { throwError(msg); } } function assertHasParent(tNode) { assertDefined(tNode, 'currentTNode should exist!'); assertDefined(tNode.parent, 'currentTNode should have a parent'); } function assertLContainer(value) { assertDefined(value, 'LContainer must be defined'); assertEqual(isLContainer(value), true, 'Expecting LContainer'); } function assertLViewOrUndefined(value) { value && assertEqual(isLView(value), true, 'Expecting LView or undefined or null'); } function assertLView(value) { assertDefined(value, 'LView must be defined'); assertEqual(isLView(value), true, 'Expecting LView'); } function assertFirstCreatePass(tView, errMessage) { assertEqual(tView.firstCreatePass, true, errMessage || 'Should only be called in first create pass.'); } function assertFirstUpdatePass(tView, errMessage) { assertEqual(tView.firstUpdatePass, true, errMessage || 'Should only be called in first update pass.'); } /** * This is a basic sanity check that an object is probably a directive def. DirectiveDef is * an interface, so we can't do a direct instanceof check. */ function assertDirectiveDef(obj) { if (obj.type === undefined || obj.selectors == undefined || obj.inputs === undefined) { throwError(`Expected a DirectiveDef/ComponentDef and this object does not seem to have the expected shape.`); } } function assertIndexInDeclRange(lView, index) { const tView = lView[1]; assertBetween(HEADER_OFFSET, tView.bindingStartIndex, index); } function assertIndexInExpandoRange(lView, index) { const tView = lView[1]; assertBetween(tView.expandoStartIndex, lView.length, index); } function assertBetween(lower, upper, index) { if (!(lower <= index && index < upper)) { throwError(`Index out of range (expecting ${lower} <= ${index} < ${upper})`); } } function assertProjectionSlots(lView, errMessage) { assertDefined(lView[DECLARATION_COMPONENT_VIEW], 'Component views should exist.'); assertDefined(lView[DECLARATION_COMPONENT_VIEW][T_HOST].projection, errMessage || 'Components with projection nodes () must have projection slots defined.'); } function assertParentView(lView, errMessage) { assertDefined(lView, errMessage || 'Component views should always have a parent view (component\'s host view)'); } /** * This is a basic sanity check that the `injectorIndex` seems to point to what looks like a * NodeInjector data structure. * * @param lView `LView` which should be checked. * @param injectorIndex index into the `LView` where the `NodeInjector` is expected. */ function assertNodeInjector(lView, injectorIndex) { assertIndexInExpandoRange(lView, injectorIndex); assertIndexInExpandoRange(lView, injectorIndex + 8 /* NodeInjectorOffset.PARENT */); assertNumber(lView[injectorIndex + 0], 'injectorIndex should point to a bloom filter'); assertNumber(lView[injectorIndex + 1], 'injectorIndex should point to a bloom filter'); assertNumber(lView[injectorIndex + 2], 'injectorIndex should point to a bloom filter'); assertNumber(lView[injectorIndex + 3], 'injectorIndex should point to a bloom filter'); assertNumber(lView[injectorIndex + 4], 'injectorIndex should point to a bloom filter'); assertNumber(lView[injectorIndex + 5], 'injectorIndex should point to a bloom filter'); assertNumber(lView[injectorIndex + 6], 'injectorIndex should point to a bloom filter'); assertNumber(lView[injectorIndex + 7], 'injectorIndex should point to a bloom filter'); assertNumber(lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */], 'injectorIndex should point to parent injector'); } function getFactoryDef(type, throwNotFound) { 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; } /** * Symbol used to tell `Signal`s apart from other functions. * * This can be used to auto-unwrap signals in various cases, or to auto-wrap non-signal values. */ const SIGNAL = Symbol('SIGNAL'); /** * Checks if the given `value` is a reactive `Signal`. * * @developerPreview */ function isSignal(value) { return typeof value === 'function' && value[SIGNAL] !== undefined; } /** * Converts `fn` into a marked signal function (where `isSignal(fn)` will be `true`), and * potentially add some set of extra properties (passed as an object record `extraApi`). */ function createSignalFromFunction(node, fn, extraApi = {}) { fn[SIGNAL] = node; // Copy properties from `extraApi` to `fn` to complete the desired API of the `Signal`. return Object.assign(fn, extraApi); } /** * The default equality function used for `signal` and `computed`, which treats objects and arrays * as never equal, and all other primitive values using identity semantics. * * This allows signals to hold non-primitive values (arrays, objects, other collections) and still * propagate change notification upon explicit mutation without identity change. * * @developerPreview */ function defaultEquals(a, b) { // `Object.is` compares two values using identity semantics which is desired behavior for // primitive values. If `Object.is` determines two values to be equal we need to make sure that // those don't represent objects (we want to make sure that 2 objects are always considered // "unequal"). The null check is needed for the special case of JavaScript reporting null values // as objects (`typeof null === 'object'`). return (a === null || typeof a !== 'object') && Object.is(a, b); } // Required as the signals library is in a separate package, so we need to explicitly ensure the /** * A `WeakRef`-compatible reference that fakes the API with a strong reference * internally. */ class LeakyRef { constructor(ref) { this.ref = ref; } deref() { return this.ref; } } // `WeakRef` is not always defined in every TS environment where Angular is compiled. Instead, // read it off of the global context if available. // tslint:disable-next-line: no-toplevel-property-access let WeakRefImpl = _global['WeakRef'] ?? LeakyRef; function newWeakRef(value) { if (typeof ngDevMode !== 'undefined' && ngDevMode && WeakRefImpl === undefined) { throw new Error(`Angular requires a browser which supports the 'WeakRef' API`); } return new WeakRefImpl(value); } function setAlternateWeakRefImpl(impl) { // no-op since the alternate impl is included by default by the framework. Remove once internal // migration is complete. } // Required as the signals library is in a separate package, so we need to explicitly ensure the /** * Counter tracking the next `ProducerId` or `ConsumerId`. */ let _nextReactiveId = 0; /** * Tracks the currently active reactive consumer (or `null` if there is no active * consumer). */ let activeConsumer = null; /** * Whether the graph is currently propagating change notifications. */ let inNotificationPhase = false; function setActiveConsumer(consumer) { const prev = activeConsumer; activeConsumer = consumer; return prev; } /** * A node in the reactive graph. * * Nodes can be producers of reactive values, consumers of other reactive values, or both. * * Producers are nodes that produce values, and can be depended upon by consumer nodes. * * Producers expose a monotonic `valueVersion` counter, and are responsible for incrementing this * version when their value semantically changes. Some producers may produce their values lazily and * thus at times need to be polled for potential updates to their value (and by extension their * `valueVersion`). This is accomplished via the `onProducerUpdateValueVersion` method for * implemented by producers, which should perform whatever calculations are necessary to ensure * `valueVersion` is up to date. * * Consumers are nodes that depend on the values of producers and are notified when those values * might have changed. * * Consumers do not wrap the reads they consume themselves, but rather can be set as the active * reader via `setActiveConsumer`. Reads of producers that happen while a consumer is active will * result in those producers being added as dependencies of that consumer node. * * The set of dependencies of a consumer is dynamic. Implementers expose a monotonically increasing * `trackingVersion` counter, which increments whenever the consumer is about to re-run any reactive * reads it needs and establish a new set of dependencies as a result. * * Producers store the last `trackingVersion` they've seen from `Consumer`s which have read them. * This allows a producer to identify whether its record of the dependency is current or stale, by * comparing the consumer's `trackingVersion` to the version at which the dependency was * last observed. */ class ReactiveNode { constructor() { this.id = _nextReactiveId++; /** * A cached weak reference to this node, which will be used in `ReactiveEdge`s. */ this.ref = newWeakRef(this); /** * Edges to producers on which this node depends (in its consumer capacity). */ this.producers = new Map(); /** * Edges to consumers on which this node depends (in its producer capacity). */ this.consumers = new Map(); /** * Monotonically increasing counter representing a version of this `Consumer`'s * dependencies. */ this.trackingVersion = 0; /** * Monotonically increasing counter which increases when the value of this `Producer` * semantically changes. */ this.valueVersion = 0; } /** * Polls dependencies of a consumer to determine if they have actually changed. * * If this returns `false`, then even though the consumer may have previously been notified of a * change, the values of its dependencies have not actually changed and the consumer should not * rerun any reactions. */ consumerPollProducersForChange() { for (const [producerId, edge] of this.producers) { const producer = edge.producerNode.deref(); if (producer === undefined || edge.atTrackingVersion !== this.trackingVersion) { // This dependency edge is stale, so remove it. this.producers.delete(producerId); producer?.consumers.delete(this.id); continue; } if (producer.producerPollStatus(edge.seenValueVersion)) { // One of the dependencies reports a real value change. return true; } } // No dependency reported a real value change, so the `Consumer` has also not been // impacted. return false; } /** * Notify all consumers of this producer that its value may have changed. */ producerMayHaveChanged() { // Prevent signal reads when we're updating the graph const prev = inNotificationPhase; inNotificationPhase = true; try { for (const [consumerId, edge] of this.consumers) { const consumer = edge.consumerNode.deref(); if (consumer === undefined || consumer.trackingVersion !== edge.atTrackingVersion) { this.consumers.delete(consumerId); consumer?.producers.delete(this.id); continue; } consumer.onConsumerDependencyMayHaveChanged(); } } finally { inNotificationPhase = prev; } } /** * Mark that this producer node has been accessed in the current reactive context. */ producerAccessed() { if (inNotificationPhase) { throw new Error(typeof ngDevMode !== 'undefined' && ngDevMode ? `Assertion error: signal read during notification phase` : ''); } if (activeConsumer === null) { return; } // Either create or update the dependency `Edge` in both directions. let edge = activeConsumer.producers.get(this.id); if (edge === undefined) { edge = { consumerNode: activeConsumer.ref, producerNode: this.ref, seenValueVersion: this.valueVersion, atTrackingVersion: activeConsumer.trackingVersion, }; activeConsumer.producers.set(this.id, edge); this.consumers.set(activeConsumer.id, edge); } else { edge.seenValueVersion = this.valueVersion; edge.atTrackingVersion = activeConsumer.trackingVersion; } } /** * Whether this consumer currently has any producers registered. */ get hasProducers() { return this.producers.size > 0; } /** * Whether this `ReactiveNode` in its producer capacity is currently allowed to initiate updates, * based on the current consumer context. */ get producerUpdatesAllowed() { return activeConsumer?.consumerAllowSignalWrites !== false; } /** * Checks if a `Producer` has a current value which is different than the value * last seen at a specific version by a `Consumer` which recorded a dependency on * this `Producer`. */ producerPollStatus(lastSeenValueVersion) { // `producer.valueVersion` may be stale, but a mismatch still means that the value // last seen by the `Consumer` is also stale. if (this.valueVersion !== lastSeenValueVersion) { return true; } // Trigger the `Producer` to update its `valueVersion` if necessary. this.onProducerUpdateValueVersion(); // At this point, we can trust `producer.valueVersion`. return this.valueVersion !== lastSeenValueVersion; } } /** * Create a computed `Signal` which derives a reactive value from an expression. * * @developerPreview */ function computed(computation, options) { const node = new ComputedImpl(computation, options?.equal ?? defaultEquals); // Casting here is required for g3, as TS inference behavior is slightly different between our // version/options and g3's. return createSignalFromFunction(node, node.signal.bind(node)); } /** * A dedicated symbol used before a computed value has been calculated for the first time. * Explicitly typed as `any` so we can use it as signal's value. */ const UNSET = Symbol('UNSET'); /** * A dedicated symbol used in place of a computed signal value to indicate that a given computation * is in progress. Used to detect cycles in computation chains. * Explicitly typed as `any` so we can use it as signal's value. */ const COMPUTING = Symbol('COMPUTING'); /** * A dedicated symbol used in place of a computed signal value to indicate that a given computation * failed. The thrown error is cached until the computation gets dirty again. * Explicitly typed as `any` so we can use it as signal's value. */ const ERRORED = Symbol('ERRORED'); /** * A computation, which derives a value from a declarative reactive expression. * * `Computed`s are both producers and consumers of reactivity. */ class ComputedImpl extends ReactiveNode { constructor(computation, equal) { super(); this.computation = computation; this.equal = equal; /** * Current value of the computation. * * This can also be one of the special values `UNSET`, `COMPUTING`, or `ERRORED`. */ this.value = UNSET; /** * If `value` is `ERRORED`, the error caught from the last computation attempt which will * be re-thrown. */ this.error = null; /** * Flag indicating that the computation is currently stale, meaning that one of the * dependencies has notified of a potential change. * * It's possible that no dependency has _actually_ changed, in which case the `stale` * state can be resolved without recomputing the value. */ this.stale = true; this.consumerAllowSignalWrites = false; } onConsumerDependencyMayHaveChanged() { if (this.stale) { // We've already notified consumers that this value has potentially changed. return; } // Record that the currently cached value may be stale. this.stale = true; // Notify any consumers about the potential change. this.producerMayHaveChanged(); } onProducerUpdateValueVersion() { if (!this.stale) { // The current value and its version are already up to date. return; } // The current value is stale. Check whether we need to produce a new one. if (this.value !== UNSET && this.value !== COMPUTING && !this.consumerPollProducersForChange()) { // Even though we were previously notified of a potential dependency update, all of // our dependencies report that they have not actually changed in value, so we can // resolve the stale state without needing to recompute the current value. this.stale = false; return; } // The current value is stale, and needs to be recomputed. It still may not change - // that depends on whether the newly computed value is equal to the old. this.recomputeValue(); } recomputeValue() { if (this.value === COMPUTING) { // Our computation somehow led to a cyclic read of itself. throw new Error('Detected cycle in computations.'); } const oldValue = this.value; this.value = COMPUTING; // As we're re-running the computation, update our dependent tracking version number. this.trackingVersion++; const prevConsumer = setActiveConsumer(this); let newValue; try { newValue = this.computation(); } catch (err) { newValue = ERRORED; this.error = err; } finally { setActiveConsumer(prevConsumer); } this.stale = false; if (oldValue !== UNSET && oldValue !== ERRORED && newValue !== ERRORED && this.equal(oldValue, newValue)) { // No change to `valueVersion` - old and new values are // semantically equivalent. this.value = oldValue; return; } this.value = newValue; this.valueVersion++; } signal() { // Check if the value needs updating before returning it. this.onProducerUpdateValueVersion(); // Record that someone looked at this signal. this.producerAccessed(); if (this.value === ERRORED) { throw this.error; } return this.value; } } function defaultThrowError() { throw new Error(); } let throwInvalidWriteToSignalErrorFn = defaultThrowError; function throwInvalidWriteToSignalError() { throwInvalidWriteToSignalErrorFn(); } function setThrowInvalidWriteToSignalError(fn) { throwInvalidWriteToSignalErrorFn = fn; } /** * If set, called after `WritableSignal`s are updated. * * This hook can be used to achieve various effects, such as running effects synchronously as part * of setting a signal. */ let postSignalSetFn = null; class WritableSignalImpl extends ReactiveNode { constructor(value, equal) { super(); this.value = value; this.equal = equal; this.consumerAllowSignalWrites = false; } onConsumerDependencyMayHaveChanged() { // This never happens for writable signals as they're not consumers. } onProducerUpdateValueVersion() { // Writable signal value versions are always up to date. } /** * Directly update the value of the signal to a new value, which may or may not be * equal to the previous. * * In the event that `newValue` is semantically equal to the current value, `set` is * a no-op. */ set(newValue) { if (!this.producerUpdatesAllowed) { throwInvalidWriteToSignalError(); } if (!this.equal(this.value, newValue)) { this.value = newValue; this.valueVersion++; this.producerMayHaveChanged(); postSignalSetFn?.(); } } /** * Derive a new value for the signal from its current value using the `updater` function. * * This is equivalent to calling `set` on the result of running `updater` on the current * value. */ update(updater) { if (!this.producerUpdatesAllowed) { throwInvalidWriteToSignalError(); } this.set(updater(this.value)); } /** * Calls `mutator` on the current value and assumes that it has been mutated. */ mutate(mutator) { if (!this.producerUpdatesAllowed) { throwInvalidWriteToSignalError(); } // Mutate bypasses equality checks as it's by definition changing the value. mutator(this.value); this.valueVersion++; this.producerMayHaveChanged(); postSignalSetFn?.(); } asReadonly() { if (this.readonlySignal === undefined) { this.readonlySignal = createSignalFromFunction(this, () => this.signal()); } return this.readonlySignal; } signal() { this.producerAccessed(); return this.value; } } /** * Create a `Signal` that can be set or updated directly. * * @developerPreview */ function signal(initialValue, options) { const signalNode = new WritableSignalImpl(initialValue, options?.equal ?? defaultEquals); // Casting here is required for g3, as TS inference behavior is slightly different between our // version/options and g3's. const signalFn = createSignalFromFunction(signalNode, signalNode.signal.bind(signalNode), { set: signalNode.set.bind(signalNode), update: signalNode.update.bind(signalNode), mutate: signalNode.mutate.bind(signalNode), asReadonly: signalNode.asReadonly.bind(signalNode) }); return signalFn; } /** * Execute an arbitrary function in a non-reactive (non-tracking) context. The executed function * can, optionally, return a value. * * @developerPreview */ function untracked(nonReactiveReadsFn) { const prevConsumer = setActiveConsumer(null); // We are not trying to catch any particular errors here, just making sure that the consumers // stack is restored in case of errors. try { return nonReactiveReadsFn(); } finally { setActiveConsumer(prevConsumer); } } const NOOP_CLEANUP_FN = () => { }; /** * Watches a reactive expression and allows it to be scheduled to re-run * when any dependencies notify of a change. * * `Watch` doesn't run reactive expressions itself, but relies on a consumer- * provided scheduling operation to coordinate calling `Watch.run()`. */ class Watch extends ReactiveNode { constructor(watch, schedule, allowSignalWrites) { super(); this.watch = watch; this.schedule = schedule; this.dirty = false; this.cleanupFn = NOOP_CLEANUP_FN; this.registerOnCleanup = (cleanupFn) => { this.cleanupFn = cleanupFn; }; this.consumerAllowSignalWrites = allowSignalWrites; } notify() { if (!this.dirty) { this.schedule(this); } this.dirty = true; } onConsumerDependencyMayHaveChanged() { this.notify(); } onProducerUpdateValueVersion() { // Watches are not producers. } /** * Execute the reactive expression in the context of this `Watch` consumer. * * Should be called by the user scheduling algorithm when the provided * `schedule` hook is called by `Watch`. */ run() { this.dirty = false; if (this.trackingVersion !== 0 && !this.consumerPollProducersForChange()) { return; } const prevConsumer = setActiveConsumer(this); this.trackingVersion++; try { this.cleanupFn(); this.cleanupFn = NOOP_CLEANUP_FN; this.watch(this.registerOnCleanup); } finally { setActiveConsumer(prevConsumer); } } cleanup() { this.cleanupFn(); } } /** * Represents a basic change from a previous to a new value for a single * property on a directive instance. Passed as a value in a * {@link SimpleChanges} object to the `ngOnChanges` hook. * * @see `OnChanges` * * @publicApi */ class SimpleChange { constructor(previousValue, currentValue, firstChange) { this.previousValue = previousValue; this.currentValue = currentValue; this.firstChange = firstChange; } /** * Check whether the new value is the first value assigned. */ isFirstChange() { return this.firstChange; } } exports('SimpleChange', SimpleChange); /** * The NgOnChangesFeature decorates a component with support for the ngOnChanges * lifecycle hook, so it should be included in any component that implements * that hook. * * If the component or directive uses inheritance, the NgOnChangesFeature MUST * be included as a feature AFTER {@link InheritDefinitionFeature}, otherwise * inherited properties will not be propagated to the ngOnChanges lifecycle * hook. * * Example usage: * * ``` * static ɵcmp = defineComponent({ * ... * inputs: {name: 'publicName'}, * features: [NgOnChangesFeature] * }); * ``` * * @codeGenApi */ function ɵɵNgOnChangesFeature() { return NgOnChangesFeatureImpl; } function NgOnChangesFeatureImpl(definition) { if (definition.type.prototype.ngOnChanges) { definition.setInput = ngOnChangesSetInput; } return rememberChangeHistoryAndInvokeOnChangesHook; } // This option ensures that the ngOnChanges lifecycle hook will be inherited // from superclasses (in InheritDefinitionFeature). /** @nocollapse */ // tslint:disable-next-line:no-toplevel-property-access ɵɵNgOnChangesFeature.ngInherit = true; /** * This is a synthetic lifecycle hook which gets inserted into `TView.preOrderHooks` to simulate * `ngOnChanges`. * * The hook reads the `NgSimpleChangesStore` data from the component instance and if changes are * found it invokes `ngOnChanges` on the component instance. * * @param this Component instance. Because this function gets inserted into `TView.preOrderHooks`, * it is guaranteed to be called with component instance. */ function rememberChangeHistoryAndInvokeOnChangesHook() { const simpleChangesStore = getSimpleChangesStore(this); const current = simpleChangesStore?.current; if (current) { const previous = simpleChangesStore.previous; if (previous === EMPTY_OBJ) { simpleChangesStore.previous = current; } else { // New changes are copied to the previous store, so that we don't lose history for inputs // which were not changed this time for (let key in current) { previous[key] = current[key]; } } simpleChangesStore.current = null; this.ngOnChanges(current); } } function ngOnChangesSetInput(instance, value, publicName, privateName) { const declaredName = this.declaredInputs[publicName]; ngDevMode && assertString(declaredName, 'Name of input in ngOnChanges has to be a string'); const simpleChangesStore = getSimpleChangesStore(instance) || setSimpleChangesStore(instance, { previous: EMPTY_OBJ, current: null }); const current = simpleChangesStore.current || (simpleChangesStore.current = {}); const previous = simpleChangesStore.previous; const previousChange = previous[declaredName]; current[declaredName] = new SimpleChange(previousChange && previousChange.currentValue, value, previous === EMPTY_OBJ); instance[privateName] = value; } const SIMPLE_CHANGES_STORE = '__ngSimpleChanges__'; function getSimpleChangesStore(instance) { return instance[SIMPLE_CHANGES_STORE] || null; } function setSimpleChangesStore(instance, store) { return instance[SIMPLE_CHANGES_STORE] = store; } let profilerCallback = null; /** * Sets the callback function which will be invoked before and after performing certain actions at * runtime (for example, before and after running change detection). * * Warning: this function is *INTERNAL* and should not be relied upon in application's code. * The contract of the function might be changed in any release and/or the function can be removed * completely. * * @param profiler function provided by the caller or null value to disable profiling. */ const setProfiler = (profiler) => { profilerCallback = profiler; }; /** * Profiler function which wraps user code executed by the runtime. * * @param event ProfilerEvent corresponding to the execution context * @param instance component instance * @param hookOrListener lifecycle hook function or output listener. The value depends on the * execution context * @returns */ const profiler = function (event, instance, hookOrListener) { if (profilerCallback != null /* both `null` and `undefined` */) { profilerCallback(event, instance, hookOrListener); } }; const SVG_NAMESPACE = 'svg'; const MATH_ML_NAMESPACE = 'math'; /** * For efficiency reasons we often put several different data types (`RNode`, `LView`, `LContainer`) * in same location in `LView`. This is because we don't want to pre-allocate space for it * because the storage is sparse. This file contains utilities for dealing with such data types. * * How do we know what is stored at a given location in `LView`. * - `Array.isArray(value) === false` => `RNode` (The normal storage value) * - `Array.isArray(value) === true` => then the `value[0]` represents the wrapped value. * - `typeof value[TYPE] === 'object'` => `LView` * - This happens when we have a component at a given location * - `typeof value[TYPE] === true` => `LContainer` * - This happens when we have `LContainer` binding at a given location. * * * NOTE: it is assumed that `Array.isArray` and `typeof` operations are very efficient. */ /** * Returns `RNode`. * @param value wrapped value of `RNode`, `LView`, `LContainer` */ function unwrapRNode(value) { while (Array.isArray(value)) { value = value[HOST]; } return value; } /** * Returns `LView` or `null` if not found. * @param value wrapped value of `RNode`, `LView`, `LContainer` */ function unwrapLView(value) { while (Array.isArray(value)) { // This check is same as `isLView()` but we don't call at as we don't want to call // `Array.isArray()` twice and give JITer more work for inlining. if (typeof value[TYPE] === 'object') return value; value = value[HOST]; } return null; } /** * Retrieves an element value from the provided `viewData`, by unwrapping * from any containers, component views, or style contexts. */ function getNativeByIndex(index, lView) { ngDevMode && assertIndexInRange(lView, index); ngDevMode && assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Expected to be past HEADER_OFFSET'); return unwrapRNode(lView[index]); } /** * Retrieve an `RNode` for a given `TNode` and `LView`. * * This function guarantees in dev mode to retrieve a non-null `RNode`. * * @param tNode * @param lView */ function getNativeByTNode(tNode, lView) { ngDevMode && assertTNodeForLView(tNode, lView); ngDevMode && assertIndexInRange(lView, tNode.index); const node = unwrapRNode(lView[tNode.index]); return node; } /** * Retrieve an `RNode` or `null` for a given `TNode` and `LView`. * * Some `TNode`s don't have associated `RNode`s. For example `Projection` * * @param tNode * @param lView */ function getNativeByTNodeOrNull(tNode, lView) { const index = tNode === null ? -1 : tNode.index; if (index !== -1) { ngDevMode && assertTNodeForLView(tNode, lView); const node = unwrapRNode(lView[index]); return node; } return null; } // fixme(misko): The return Type should be `TNode|null` function getTNode(tView, index) { ngDevMode && assertGreaterThan(index, -1, 'wrong index for TNode'); ngDevMode && assertLessThan(index, tView.data.length, 'wrong index for TNode'); const tNode = tView.data[index]; ngDevMode && tNode !== null && assertTNode(tNode); return tNode; } /** Retrieves a value from any `LView` or `TData`. */ function load(view, index) { ngDevMode && assertIndexInRange(view, index); return view[index]; } function getComponentLViewByIndex(nodeIndex, hostView) { // Could be an LView or an LContainer. If LContainer, unwrap to find LView. ngDevMode && assertIndexInRange(hostView, nodeIndex); const slotValue = hostView[nodeIndex]; const lView = isLView(slotValue) ? slotValue : slotValue[HOST]; return lView; } /** Checks whether a given view is in creation mode */ function isCreationMode(view) { return (view[FLAGS] & 4 /* LViewFlags.CreationMode */) === 4 /* LViewFlags.CreationMode */; } /** * Returns a boolean for whether the view is attached to the change detection tree. * * Note: This determines whether a view should be checked, not whether it's inserted * into a container. For that, you'll want `viewAttachedToContainer` below. */ function viewAttachedToChangeDetector(view) { return (view[FLAGS] & 128 /* LViewFlags.Attached */) === 128 /* LViewFlags.Attached */; } /** Returns a boolean for whether the view is attached to a container. */ function viewAttachedToContainer(view) { return isLContainer(view[PARENT]); } function getConstant(consts, index) { if (index === null || index === undefined) return null; ngDevMode && assertIndexInRange(consts, index); return consts[index]; } /** * Resets the pre-order hook flags of the view. * @param lView the LView on which the flags are reset */ function resetPreOrderHookFlags(lView) { lView[PREORDER_HOOK_FLAGS] = 0; } /** * Adds the `RefreshView` flag from the lView and updates DESCENDANT_VIEWS_TO_REFRESH counters of * parents. */ function markViewForRefresh(lView) { if ((lView[FLAGS] & 1024 /* LViewFlags.RefreshView */) === 0) { lView[FLAGS] |= 1024 /* LViewFlags.RefreshView */; updateViewsToRefresh(lView, 1); } } /** * Removes the `RefreshView` flag from the lView and updates DESCENDANT_VIEWS_TO_REFRESH counters of * parents. */ function clearViewRefreshFlag(lView) { if (lView[FLAGS] & 1024 /* LViewFlags.RefreshView */) { lView[FLAGS] &= ~1024 /* LViewFlags.RefreshView */; updateViewsToRefresh(lView, -1); } } /** * Updates the `DESCENDANT_VIEWS_TO_REFRESH` counter on the parents of the `LView` as well as the * parents above that whose * 1. counter goes from 0 to 1, indicating that there is a new child that has a view to refresh * or * 2. counter goes from 1 to 0, indicating there are no more descendant views to refresh */ function updateViewsToRefresh(lView, amount) { let parent = lView[PARENT]; if (parent === null) { return; } parent[DESCENDANT_VIEWS_TO_REFRESH] += amount; let viewOrContainer = parent; parent = parent[PARENT]; while (parent !== null && ((amount === 1 && viewOrContainer[DESCENDANT_VIEWS_TO_REFRESH] === 1) || (amount === -1 && viewOrContainer[DESCENDANT_VIEWS_TO_REFRESH] === 0))) { parent[DESCENDANT_VIEWS_TO_REFRESH] += amount; viewOrContainer = parent; parent = parent[PARENT]; } } /** * Stores a LView-specific destroy callback. */ function storeLViewOnDestroy(lView, onDestroyCallback) { if ((lView[FLAGS] & 256 /* LViewFlags.Destroyed */) === 256 /* LViewFlags.Destroyed */) { throw new RuntimeError(911 /* RuntimeErrorCode.VIEW_ALREADY_DESTROYED */, ngDevMode && 'View has already been destroyed.'); } if (lView[ON_DESTROY_HOOKS] === null) { lView[ON_DESTROY_HOOKS] = []; } lView[ON_DESTROY_HOOKS].push(onDestroyCallback); } /** * Removes previously registered LView-specific destroy callback. */ function removeLViewOnDestroy(lView, onDestroyCallback) { if (lView[ON_DESTROY_HOOKS] === null) return; const destroyCBIdx = lView[ON_DESTROY_HOOKS].indexOf(onDestroyCallback); if (destroyCBIdx !== -1) { lView[ON_DESTROY_HOOKS].splice(destroyCBIdx, 1); } } const instructionState = { lFrame: createLFrame(null), bindingsEnabled: true, skipHydrationRootTNode: null, }; /** * In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error. * * Necessary to support ChangeDetectorRef.checkNoChanges(). * * The `checkNoChanges` function is invoked only in ngDevMode=true and verifies that no unintended * changes exist in the change detector or its children. */ let _isInCheckNoChangesMode = false; function getElementDepthCount() { return instructionState.lFrame.elementDepthCount; } function increaseElementDepthCount() { instructionState.lFrame.elementDepthCount++; } function decreaseElementDepthCount() { instructionState.lFrame.elementDepthCount--; } function getBindingsEnabled() { return instructionState.bindingsEnabled; } /** * Returns true if currently inside a skip hydration block. * @returns boolean */ function isInSkipHydrationBlock$1() { return instructionState.skipHydrationRootTNode !== null; } /** * Returns true if this is the root TNode of the skip hydration block. * @param tNode the current TNode * @returns boolean */ function isSkipHydrationRootTNode(tNode) { return instructionState.skipHydrationRootTNode === tNode; } /** * Enables directive matching on elements. * * * Example: * ``` * * Should match component / directive. * *

* * * Should not match component / directive because we are in ngNonBindable. * * *
* ``` * * @codeGenApi */ function ɵɵenableBindings() { instructionState.bindingsEnabled = true; } /** * Sets a flag to specify that the TNode is in a skip hydration block. * @param tNode the current TNode */ function enterSkipHydrationBlock(tNode) { instructionState.skipHydrationRootTNode = tNode; } /** * Disables directive matching on element. * * * Example: * ``` * * Should match component / directive. * *
* * * Should not match component / directive because we are in ngNonBindable. * * *
* ``` * * @codeGenApi */ function ɵɵdisableBindings() { instructionState.bindingsEnabled = false; } /** * Clears the root skip hydration node when leaving a skip hydration block. */ function leaveSkipHydrationBlock() { instructionState.skipHydrationRootTNode = null; } /** * Return the current `LView`. */ function getLView() { return instructionState.lFrame.lView; } /** * Return the current `TView`. */ function getTView() { return instructionState.lFrame.tView; } /** * Restores `contextViewData` to the given OpaqueViewState instance. * * Used in conjunction with the getCurrentView() instruction to save a snapshot * of the current view and restore it when listeners are invoked. This allows * walking the declaration view tree in listeners to get vars from parent views. * * @param viewToRestore The OpaqueViewState instance to restore. * @returns Context of the restored OpaqueViewState instance. * * @codeGenApi */ function ɵɵrestoreView(viewToRestore) { instructionState.lFrame.contextLView = viewToRestore; return viewToRestore[CONTEXT]; } /** * Clears the view set in `ɵɵrestoreView` from memory. Returns the passed in * value so that it can be used as a return value of an instruction. * * @codeGenApi */ function ɵɵresetView(value) { instructionState.lFrame.contextLView = null; return value; } function getCurrentTNode() { let currentTNode = getCurrentTNodePlaceholderOk(); while (currentTNode !== null && currentTNode.type === 64 /* TNodeType.Placeholder */) { currentTNode = currentTNode.parent; } return currentTNode; } function getCurrentTNodePlaceholderOk() { return instructionState.lFrame.currentTNode; } function getCurrentParentTNode() { const lFrame = instructionState.lFrame; const currentTNode = lFrame.currentTNode; return lFrame.isParent ? currentTNode : currentTNode.parent; } function setCurrentTNode(tNode, isParent) { ngDevMode && tNode && assertTNodeForTView(tNode, instructionState.lFrame.tView); const lFrame = instructionState.lFrame; lFrame.currentTNode = tNode; lFrame.isParent = isParent; } function isCurrentTNodeParent() { return instructionState.lFrame.isParent; } function setCurrentTNodeAsNotParent() { instructionState.lFrame.isParent = false; } function getContextLView() { const contextLView = instructionState.lFrame.contextLView; ngDevMode && assertDefined(contextLView, 'contextLView must be defined.'); return contextLView; } function isInCheckNoChangesMode() { !ngDevMode && throwError('Must never be called in production mode'); return _isInCheckNoChangesMode; } function setIsInCheckNoChangesMode(mode) { !ngDevMode && throwError('Must never be called in production mode'); _isInCheckNoChangesMode = mode; } // top level variables should not be exported for performance reasons (PERF_NOTES.md) function getBindingRoot() { const lFrame = instructionState.lFrame; let index = lFrame.bindingRootIndex; if (index === -1) { index = lFrame.bindingRootIndex = lFrame.tView.bindingStartIndex; } return index; } function getBindingIndex() { return instructionState.lFrame.bindingIndex; } function setBindingIndex(value) { return instructionState.lFrame.bindingIndex = value; } function nextBindingIndex() { return instructionState.lFrame.bindingIndex++; } function incrementBindingIndex(count) { const lFrame = instructionState.lFrame; const index = lFrame.bindingIndex; lFrame.bindingIndex = lFrame.bindingIndex + count; return index; } function isInI18nBlock() { return instructionState.lFrame.inI18n; } function setInI18nBlock(isInI18nBlock) { instructionState.lFrame.inI18n = isInI18nBlock; } /** * Set a new binding root index so that host template functions can execute. * * Bindings inside the host template are 0 index. But because we don't know ahead of time * how many host bindings we have we can't pre-compute them. For this reason they are all * 0 index and we just shift the root so that they match next available location in the LView. * * @param bindingRootIndex Root index for `hostBindings` * @param currentDirectiveIndex `TData[currentDirectiveIndex]` will point to the current directive * whose `hostBindings` are being processed. */ function setBindingRootForHostBindings(bindingRootIndex, currentDirectiveIndex) { const lFrame = instructionState.lFrame; lFrame.bindingIndex = lFrame.bindingRootIndex = bindingRootIndex; setCurrentDirectiveIndex(currentDirectiveIndex); } /** * When host binding is executing this points to the directive index. * `TView.data[getCurrentDirectiveIndex()]` is `DirectiveDef` * `LView[getCurrentDirectiveIndex()]` is directive instance. */ function getCurrentDirectiveIndex() { return instructionState.lFrame.currentDirectiveIndex; } /** * Sets an index of a directive whose `hostBindings` are being processed. * * @param currentDirectiveIndex `TData` index where current directive instance can be found. */ function setCurrentDirectiveIndex(currentDirectiveIndex) { instructionState.lFrame.currentDirectiveIndex = currentDirectiveIndex; } /** * Retrieve the current `DirectiveDef` which is active when `hostBindings` instruction is being * executed. * * @param tData Current `TData` where the `DirectiveDef` will be looked up at. */ function getCurrentDirectiveDef(tData) { const currentDirectiveIndex = instructionState.lFrame.currentDirectiveIndex; return currentDirectiveIndex === -1 ? null : tData[currentDirectiveIndex]; } function getCurrentQueryIndex() { return instructionState.lFrame.currentQueryIndex; } function setCurrentQueryIndex(value) { instructionState.lFrame.currentQueryIndex = value; } /** * Returns a `TNode` of the location where the current `LView` is declared at. * * @param lView an `LView` that we want to find parent `TNode` for. */ function getDeclarationTNode(lView) { const tView = lView[TVIEW]; // Return the declaration parent for embedded views if (tView.type === 2 /* TViewType.Embedded */) { ngDevMode && assertDefined(tView.declTNode, 'Embedded TNodes should have declaration parents.'); return tView.declTNode; } // Components don't have `TView.declTNode` because each instance of component could be // inserted in different location, hence `TView.declTNode` is meaningless. // Falling back to `T_HOST` in case we cross component boundary. if (tView.type === 1 /* TViewType.Component */) { return lView[T_HOST]; } // Remaining TNode type is `TViewType.Root` which doesn't have a parent TNode. return null; } /** * This is a light weight version of the `enterView` which is needed by the DI system. * * @param lView `LView` location of the DI context. * @param tNode `TNode` for DI context * @param flags DI context flags. if `SkipSelf` flag is set than we walk up the declaration * tree from `tNode` until we find parent declared `TElementNode`. * @returns `true` if we have successfully entered DI associated with `tNode` (or with declared * `TNode` if `flags` has `SkipSelf`). Failing to enter DI implies that no associated * `NodeInjector` can be found and we should instead use `ModuleInjector`. * - If `true` than this call must be fallowed by `leaveDI` * - If `false` than this call failed and we should NOT call `leaveDI` */ function enterDI(lView, tNode, flags) { ngDevMode && assertLViewOrUndefined(lView); if (flags & InjectFlags.SkipSelf) { ngDevMode && assertTNodeForTView(tNode, lView[TVIEW]); let parentTNode = tNode; let parentLView = lView; while (true) { ngDevMode && assertDefined(parentTNode, 'Parent TNode should be defined'); parentTNode = parentTNode.parent; if (parentTNode === null && !(flags & InjectFlags.Host)) { parentTNode = getDeclarationTNode(parentLView); if (parentTNode === null) break; // In this case, a parent exists and is definitely an element. So it will definitely // have an existing lView as the declaration view, which is why we can assume it's defined. ngDevMode && assertDefined(parentLView, 'Parent LView should be defined'); parentLView = parentLView[DECLARATION_VIEW]; // In Ivy there are Comment nodes that correspond to ngIf and NgFor embedded directives // We want to skip those and look only at Elements and ElementContainers to ensure // we're looking at true parent nodes, and not content or other types. if (parentTNode.type & (2 /* TNodeType.Element */ | 8 /* TNodeType.ElementContainer */)) { break; } } else { break; } } if (parentTNode === null) { // If we failed to find a parent TNode this means that we should use module injector. return false; } else { tNode = parentTNode; lView = parentLView; } } ngDevMode && assertTNodeForLView(tNode, lView); const lFrame = instructionState.lFrame = allocLFrame(); lFrame.currentTNode = tNode; lFrame.lView = lView; return true; } /** * Swap the current lView with a new lView. * * For performance reasons we store the lView in the top level of the module. * This way we minimize the number of properties to read. Whenever a new view * is entered we have to store the lView for later, and when the view is * exited the state has to be restored * * @param newView New lView to become active * @returns the previously active lView; */ function enterView(newView) { ngDevMode && assertNotEqual(newView[0], newView[1], '????'); ngDevMode && assertLViewOrUndefined(newView); const newLFrame = allocLFrame(); if (ngDevMode) { assertEqual(newLFrame.isParent, true, 'Expected clean LFrame'); assertEqual(newLFrame.lView, null, 'Expected clean LFrame'); assertEqual(newLFrame.tView, null, 'Expected clean LFrame'); assertEqual(newLFrame.selectedIndex, -1, 'Expected clean LFrame'); assertEqual(newLFrame.elementDepthCount, 0, 'Expected clean LFrame'); assertEqual(newLFrame.currentDirectiveIndex, -1, 'Expected clean LFrame'); assertEqual(newLFrame.currentNamespace, null, 'Expected clean LFrame'); assertEqual(newLFrame.bindingRootIndex, -1, 'Expected clean LFrame'); assertEqual(newLFrame.currentQueryIndex, 0, 'Expected clean LFrame'); } const tView = newView[TVIEW]; instructionState.lFrame = newLFrame; ngDevMode && tView.firstChild && assertTNodeForTView(tView.firstChild, tView); newLFrame.currentTNode = tView.firstChild; newLFrame.lView = newView; newLFrame.tView = tView; newLFrame.contextLView = newView; newLFrame.bindingIndex = tView.bindingStartIndex; newLFrame.inI18n = false; } /** * Allocates next free LFrame. This function tries to reuse the `LFrame`s to lower memory pressure. */ function allocLFrame() { const currentLFrame = instructionState.lFrame; const childLFrame = currentLFrame === null ? null : currentLFrame.child; const newLFrame = childLFrame === null ? createLFrame(currentLFrame) : childLFrame; return newLFrame; } function createLFrame(parent) { const lFrame = { currentTNode: null, isParent: true, lView: null, tView: null, selectedIndex: -1, contextLView: null, elementDepthCount: 0, currentNamespace: null, currentDirectiveIndex: -1, bindingRootIndex: -1, bindingIndex: -1, currentQueryIndex: 0, parent: parent, child: null, inI18n: false, }; parent !== null && (parent.child = lFrame); // link the new LFrame for reuse. return lFrame; } /** * A lightweight version of leave which is used with DI. * * This function only resets `currentTNode` and `LView` as those are the only properties * used with DI (`enterDI()`). * * NOTE: This function is reexported as `leaveDI`. However `leaveDI` has return type of `void` where * as `leaveViewLight` has `LFrame`. This is so that `leaveViewLight` can be used in `leaveView`. */ function leaveViewLight() { const oldLFrame = instructionState.lFrame; instructionState.lFrame = oldLFrame.parent; oldLFrame.currentTNode = null; oldLFrame.lView = null; return oldLFrame; } /** * This is a lightweight version of the `leaveView` which is needed by the DI system. * * NOTE: this function is an alias so that we can change the type of the function to have `void` * return type. */ const leaveDI = leaveViewLight; /** * Leave the current `LView` * * This pops the `LFrame` with the associated `LView` from the stack. * * IMPORTANT: We must zero out the `LFrame` values here otherwise they will be retained. This is * because for performance reasons we don't release `LFrame` but rather keep it for next use. */ function leaveView() { const oldLFrame = leaveViewLight(); oldLFrame.isParent = true; oldLFrame.tView = null; oldLFrame.selectedIndex = -1; oldLFrame.contextLView = null; oldLFrame.elementDepthCount = 0; oldLFrame.currentDirectiveIndex = -1; oldLFrame.currentNamespace = null; oldLFrame.bindingRootIndex = -1; oldLFrame.bindingIndex = -1; oldLFrame.currentQueryIndex = 0; } function nextContextImpl(level) { const contextLView = instructionState.lFrame.contextLView = walkUpViews(level, instructionState.lFrame.contextLView); return contextLView[CONTEXT]; } function walkUpViews(nestingLevel, currentView) { while (nestingLevel > 0) { ngDevMode && assertDefined(currentView[DECLARATION_VIEW], 'Declaration view should be defined if nesting level is greater than 0.'); currentView = currentView[DECLARATION_VIEW]; nestingLevel--; } return currentView; } /** * Gets the currently selected element index. * * Used with {@link property} instruction (and more in the future) to identify the index in the * current `LView` to act on. */ function getSelectedIndex() { return instructionState.lFrame.selectedIndex; } /** * Sets the most recent index passed to {@link select} * * Used with {@link property} instruction (and more in the future) to identify the index in the * current `LView` to act on. * * (Note that if an "exit function" was set earlier (via `setElementExitFn()`) then that will be * run if and when the provided `index` value is different from the current selected index value.) */ function setSelectedIndex(index) { ngDevMode && index !== -1 && assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Index must be past HEADER_OFFSET (or -1).'); ngDevMode && assertLessThan(index, instructionState.lFrame.lView.length, 'Can\'t set index passed end of LView'); instructionState.lFrame.selectedIndex = index; } /** * Gets the `tNode` that represents currently selected element. */ function getSelectedTNode() { const lFrame = instructionState.lFrame; return getTNode(lFrame.tView, lFrame.selectedIndex); } /** * Sets the namespace used to create elements to `'http://www.w3.org/2000/svg'` in global state. * * @codeGenApi */ function ɵɵnamespaceSVG() { instructionState.lFrame.currentNamespace = SVG_NAMESPACE; } /** * Sets the namespace used to create elements to `'http://www.w3.org/1998/MathML/'` in global state. * * @codeGenApi */ function ɵɵnamespaceMathML() { instructionState.lFrame.currentNamespace = MATH_ML_NAMESPACE; } /** * Sets the namespace used to create elements to `null`, which forces element creation to use * `createElement` rather than `createElementNS`. * * @codeGenApi */ function ɵɵnamespaceHTML() { namespaceHTMLInternal(); } /** * Sets the namespace used to create elements to `null`, which forces element creation to use * `createElement` rather than `createElementNS`. */ function namespaceHTMLInternal() { instructionState.lFrame.currentNamespace = null; } function getNamespace$1() { return instructionState.lFrame.currentNamespace; } let _wasLastNodeCreated = true; /** * Retrieves a global flag that indicates whether the most recent DOM node * was created or hydrated. */ function wasLastNodeCreated() { return _wasLastNodeCreated; } /** * Sets a global flag to indicate whether the most recent DOM node * was created or hydrated. */ function lastNodeWasCreated(flag) { _wasLastNodeCreated = flag; } /** * Adds all directive lifecycle hooks from the given `DirectiveDef` to the given `TView`. * * Must be run *only* on the first template pass. * * Sets up the pre-order hooks on the provided `tView`, * see {@link HookData} for details about the data structure. * * @param directiveIndex The index of the directive in LView * @param directiveDef The definition containing the hooks to setup in tView * @param tView The current TView */ function registerPreOrderHooks(directiveIndex, directiveDef, tView) { ngDevMode && assertFirstCreatePass(tView); const { ngOnChanges, ngOnInit, ngDoCheck } = directiveDef.type.prototype; if (ngOnChanges) { const wrappedOnChanges = NgOnChangesFeatureImpl(directiveDef); (tView.preOrderHooks ??= []).push(directiveIndex, wrappedOnChanges); (tView.preOrderCheckHooks ??= []).push(directiveIndex, wrappedOnChanges); } if (ngOnInit) { (tView.preOrderHooks ??= []).push(0 - directiveIndex, ngOnInit); } if (ngDoCheck) { (tView.preOrderHooks ??= []).push(directiveIndex, ngDoCheck); (tView.preOrderCheckHooks ??= []).push(directiveIndex, ngDoCheck); } } /** * * Loops through the directives on the provided `tNode` and queues hooks to be * run that are not initialization hooks. * * Should be executed during `elementEnd()` and similar to * preserve hook execution order. Content, view, and destroy hooks for projected * components and directives must be called *before* their hosts. * * Sets up the content, view, and destroy hooks on the provided `tView`, * see {@link HookData} for details about the data structure. * * NOTE: This does not set up `onChanges`, `onInit` or `doCheck`, those are set up * separately at `elementStart`. * * @param tView The current TView * @param tNode The TNode whose directives are to be searched for hooks to queue */ function registerPostOrderHooks(tView, tNode) { ngDevMode && assertFirstCreatePass(tView); // It's necessary to loop through the directives at elementEnd() (rather than processing in // directiveCreate) so we can preserve the current hook order. Content, view, and destroy // hooks for projected components and directives must be called *before* their hosts. for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) { const directiveDef = tView.data[i]; ngDevMode && assertDefined(directiveDef, 'Expecting DirectiveDef'); const lifecycleHooks = directiveDef.type.prototype; const { ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit, ngAfterViewChecked, ngOnDestroy } = lifecycleHooks; if (ngAfterContentInit) { (tView.contentHooks ??= []).push(-i, ngAfterContentInit); } if (ngAfterContentChecked) { (tView.contentHooks ??= []).push(i, ngAfterContentChecked); (tView.contentCheckHooks ??= []).push(i, ngAfterContentChecked); } if (ngAfterViewInit) { (tView.viewHooks ??= []).push(-i, ngAfterViewInit); } if (ngAfterViewChecked) { (tView.viewHooks ??= []).push(i, ngAfterViewChecked); (tView.viewCheckHooks ??= []).push(i, ngAfterViewChecked); } if (ngOnDestroy != null) { (tView.destroyHooks ??= []).push(i, ngOnDestroy); } } } /** * Executing hooks requires complex logic as we need to deal with 2 constraints. * * 1. Init hooks (ngOnInit, ngAfterContentInit, ngAfterViewInit) must all be executed once and only * once, across many change detection cycles. This must be true even if some hooks throw, or if * some recursively trigger a change detection cycle. * To solve that, it is required to track the state of the execution of these init hooks. * This is done by storing and maintaining flags in the view: the {@link InitPhaseState}, * and the index within that phase. They can be seen as a cursor in the following structure: * [[onInit1, onInit2], [afterContentInit1], [afterViewInit1, afterViewInit2, afterViewInit3]] * They are are stored as flags in LView[FLAGS]. * * 2. Pre-order hooks can be executed in batches, because of the select instruction. * To be able to pause and resume their execution, we also need some state about the hook's array * that is being processed: * - the index of the next hook to be executed * - the number of init hooks already found in the processed part of the array * They are are stored as flags in LView[PREORDER_HOOK_FLAGS]. */ /** * Executes pre-order check hooks ( OnChanges, DoChanges) given a view where all the init hooks were * executed once. This is a light version of executeInitAndCheckPreOrderHooks where we can skip read * / write of the init-hooks related flags. * @param lView The LView where hooks are defined * @param hooks Hooks to be run * @param nodeIndex 3 cases depending on the value: * - undefined: all hooks from the array should be executed (post-order case) * - null: execute hooks only from the saved index until the end of the array (pre-order case, when * flushing the remaining hooks) * - number: execute hooks only from the saved index until that node index exclusive (pre-order * case, when executing select(number)) */ function executeCheckHooks(lView, hooks, nodeIndex) { callHooks(lView, hooks, 3 /* InitPhaseState.InitPhaseCompleted */, nodeIndex); } /** * Executes post-order init and check hooks (one of AfterContentInit, AfterContentChecked, * AfterViewInit, AfterViewChecked) given a view where there are pending init hooks to be executed. * @param lView The LView where hooks are defined * @param hooks Hooks to be run * @param initPhase A phase for which hooks should be run * @param nodeIndex 3 cases depending on the value: * - undefined: all hooks from the array should be executed (post-order case) * - null: execute hooks only from the saved index until the end of the array (pre-order case, when * flushing the remaining hooks) * - number: execute hooks only from the saved index until that node index exclusive (pre-order * case, when executing select(number)) */ function executeInitAndCheckHooks(lView, hooks, initPhase, nodeIndex) { ngDevMode && assertNotEqual(initPhase, 3 /* InitPhaseState.InitPhaseCompleted */, 'Init pre-order hooks should not be called more than once'); if ((lView[FLAGS] & 3 /* LViewFlags.InitPhaseStateMask */) === initPhase) { callHooks(lView, hooks, initPhase, nodeIndex); } } function incrementInitPhaseFlags(lView, initPhase) { ngDevMode && assertNotEqual(initPhase, 3 /* InitPhaseState.InitPhaseCompleted */, 'Init hooks phase should not be incremented after all init hooks have been run.'); let flags = lView[FLAGS]; if ((flags & 3 /* LViewFlags.InitPhaseStateMask */) === initPhase) { flags &= 4095 /* LViewFlags.IndexWithinInitPhaseReset */; flags += 1 /* LViewFlags.InitPhaseStateIncrementer */; lView[FLAGS] = flags; } } /** * Calls lifecycle hooks with their contexts, skipping init hooks if it's not * the first LView pass * * @param currentView The current view * @param arr The array in which the hooks are found * @param initPhaseState the current state of the init phase * @param currentNodeIndex 3 cases depending on the value: * - undefined: all hooks from the array should be executed (post-order case) * - null: execute hooks only from the saved index until the end of the array (pre-order case, when * flushing the remaining hooks) * - number: execute hooks only from the saved index until that node index exclusive (pre-order * case, when executing select(number)) */ function callHooks(currentView, arr, initPhase, currentNodeIndex) { ngDevMode && assertEqual(isInCheckNoChangesMode(), false, 'Hooks should never be run when in check no changes mode.'); const startIndex = currentNodeIndex !== undefined ? (currentView[PREORDER_HOOK_FLAGS] & 65535 /* PreOrderHookFlags.IndexOfTheNextPreOrderHookMaskMask */) : 0; const nodeIndexLimit = currentNodeIndex != null ? currentNodeIndex : -1; const max = arr.length - 1; // Stop the loop at length - 1, because we look for the hook at i + 1 let lastNodeIndexFound = 0; for (let i = startIndex; i < max; i++) { const hook = arr[i + 1]; if (typeof hook === 'number') { lastNodeIndexFound = arr[i]; if (currentNodeIndex != null && lastNodeIndexFound >= currentNodeIndex) { break; } } else { const isInitHook = arr[i] < 0; if (isInitHook) { currentView[PREORDER_HOOK_FLAGS] += 65536 /* PreOrderHookFlags.NumberOfInitHooksCalledIncrementer */; } if (lastNodeIndexFound < nodeIndexLimit || nodeIndexLimit == -1) { callHook(currentView, initPhase, arr, i); currentView[PREORDER_HOOK_FLAGS] = (currentView[PREORDER_HOOK_FLAGS] & 4294901760 /* PreOrderHookFlags.NumberOfInitHooksCalledMask */) + i + 2; } i++; } } } /** * Executes a single lifecycle hook, making sure that: * - it is called in the non-reactive context; * - profiling data are registered. */ function callHookInternal(directive, hook) { profiler(4 /* ProfilerEvent.LifecycleHookStart */, directive, hook); const prevConsumer = setActiveConsumer(null); try { hook.call(directive); } finally { setActiveConsumer(prevConsumer); profiler(5 /* ProfilerEvent.LifecycleHookEnd */, directive, hook); } } /** * Execute one hook against the current `LView`. * * @param currentView The current view * @param initPhaseState the current state of the init phase * @param arr The array in which the hooks are found * @param i The current index within the hook data array */ function callHook(currentView, initPhase, arr, i) { const isInitHook = arr[i] < 0; const hook = arr[i + 1]; const directiveIndex = isInitHook ? -arr[i] : arr[i]; const directive = currentView[directiveIndex]; if (isInitHook) { const indexWithintInitPhase = currentView[FLAGS] >> 12 /* LViewFlags.IndexWithinInitPhaseShift */; // The init phase state must be always checked here as it may have been recursively updated. if (indexWithintInitPhase < (currentView[PREORDER_HOOK_FLAGS] >> 16 /* PreOrderHookFlags.NumberOfInitHooksCalledShift */) && (currentView[FLAGS] & 3 /* LViewFlags.InitPhaseStateMask */) === initPhase) { currentView[FLAGS] += 4096 /* LViewFlags.IndexWithinInitPhaseIncrementer */; callHookInternal(directive, hook); } } else { callHookInternal(directive, hook); } } const NO_PARENT_INJECTOR = -1; /** * Each injector is saved in 9 contiguous slots in `LView` and 9 contiguous slots in * `TView.data`. This allows us to store information about the current node's tokens (which * can be shared in `TView`) as well as the tokens of its ancestor nodes (which cannot be * shared, so they live in `LView`). * * Each of these slots (aside from the last slot) contains a bloom filter. This bloom filter * determines whether a directive is available on the associated node or not. This prevents us * from searching the directives array at this level unless it's probable the directive is in it. * * See: https://en.wikipedia.org/wiki/Bloom_filter for more about bloom filters. * * Because all injectors have been flattened into `LView` and `TViewData`, they cannot typed * using interfaces as they were previously. The start index of each `LInjector` and `TInjector` * will differ based on where it is flattened into the main array, so it's not possible to know * the indices ahead of time and save their types here. The interfaces are still included here * for documentation purposes. * * export interface LInjector extends Array { * * // Cumulative bloom for directive IDs 0-31 (IDs are % BLOOM_SIZE) * [0]: number; * * // Cumulative bloom for directive IDs 32-63 * [1]: number; * * // Cumulative bloom for directive IDs 64-95 * [2]: number; * * // Cumulative bloom for directive IDs 96-127 * [3]: number; * * // Cumulative bloom for directive IDs 128-159 * [4]: number; * * // Cumulative bloom for directive IDs 160 - 191 * [5]: number; * * // Cumulative bloom for directive IDs 192 - 223 * [6]: number; * * // Cumulative bloom for directive IDs 224 - 255 * [7]: number; * * // We need to store a reference to the injector's parent so DI can keep looking up * // the injector tree until it finds the dependency it's looking for. * [PARENT_INJECTOR]: number; * } * * export interface TInjector extends Array { * * // Shared node bloom for directive IDs 0-31 (IDs are % BLOOM_SIZE) * [0]: number; * * // Shared node bloom for directive IDs 32-63 * [1]: number; * * // Shared node bloom for directive IDs 64-95 * [2]: number; * * // Shared node bloom for directive IDs 96-127 * [3]: number; * * // Shared node bloom for directive IDs 128-159 * [4]: number; * * // Shared node bloom for directive IDs 160 - 191 * [5]: number; * * // Shared node bloom for directive IDs 192 - 223 * [6]: number; * * // Shared node bloom for directive IDs 224 - 255 * [7]: number; * * // Necessary to find directive indices for a particular node. * [TNODE]: TElementNode|TElementContainerNode|TContainerNode; * } */ /** * Factory for creating instances of injectors in the NodeInjector. * * This factory is complicated by the fact that it can resolve `multi` factories as well. * * NOTE: Some of the fields are optional which means that this class has two hidden classes. * - One without `multi` support (most common) * - One with `multi` values, (rare). * * Since VMs can cache up to 4 inline hidden classes this is OK. * * - Single factory: Only `resolving` and `factory` is defined. * - `providers` factory: `componentProviders` is a number and `index = -1`. * - `viewProviders` factory: `componentProviders` is a number and `index` points to `providers`. */ class NodeInjectorFactory { constructor( /** * Factory to invoke in order to create a new instance. */ factory, /** * Set to `true` if the token is declared in `viewProviders` (or if it is component). */ isViewProvider, injectImplementation) { this.factory = factory; /** * Marker set to true during factory invocation to see if we get into recursive loop. * Recursive loop causes an error to be displayed. */ this.resolving = false; ngDevMode && assertDefined(factory, 'Factory not specified'); ngDevMode && assertEqual(typeof factory, 'function', 'Expected factory function.'); this.canSeeViewProviders = isViewProvider; this.injectImpl = injectImplementation; } } function isFactory(obj) { return obj instanceof NodeInjectorFactory; } /** * Converts `TNodeType` into human readable text. * Make sure this matches with `TNodeType` */ function toTNodeTypeAsString(tNodeType) { let text = ''; (tNodeType & 1 /* TNodeType.Text */) && (text += '|Text'); (tNodeType & 2 /* TNodeType.Element */) && (text += '|Element'); (tNodeType & 4 /* TNodeType.Container */) && (text += '|Container'); (tNodeType & 8 /* TNodeType.ElementContainer */) && (text += '|ElementContainer'); (tNodeType & 16 /* TNodeType.Projection */) && (text += '|Projection'); (tNodeType & 32 /* TNodeType.Icu */) && (text += '|IcuContainer'); (tNodeType & 64 /* TNodeType.Placeholder */) && (text += '|Placeholder'); return text.length > 0 ? text.substring(1) : text; } /** * Returns `true` if the `TNode` has a directive which has `@Input()` for `class` binding. * * ``` *
* ``` * and * ``` * @Directive({ * }) * class MyDirective { * @Input() * class: string; * } * ``` * * In the above case it is necessary to write the reconciled styling information into the * directive's input. * * @param tNode */ function hasClassInput(tNode) { return (tNode.flags & 8 /* TNodeFlags.hasClassInput */) !== 0; } /** * Returns `true` if the `TNode` has a directive which has `@Input()` for `style` binding. * * ``` *
* ``` * and * ``` * @Directive({ * }) * class MyDirective { * @Input() * class: string; * } * ``` * * In the above case it is necessary to write the reconciled styling information into the * directive's input. * * @param tNode */ function hasStyleInput(tNode) { return (tNode.flags & 16 /* TNodeFlags.hasStyleInput */) !== 0; } function assertTNodeType(tNode, expectedTypes, message) { assertDefined(tNode, 'should be called with a TNode'); if ((tNode.type & expectedTypes) === 0) { throwError(message || `Expected [${toTNodeTypeAsString(expectedTypes)}] but got ${toTNodeTypeAsString(tNode.type)}.`); } } function assertPureTNodeType(type) { if (!(type === 2 /* TNodeType.Element */ || // type === 1 /* TNodeType.Text */ || // type === 4 /* TNodeType.Container */ || // type === 8 /* TNodeType.ElementContainer */ || // type === 32 /* TNodeType.Icu */ || // type === 16 /* TNodeType.Projection */ || // type === 64 /* TNodeType.Placeholder */)) { throwError(`Expected TNodeType to have only a single type selected, but got ${toTNodeTypeAsString(type)}.`); } } /// Parent Injector Utils /////////////////////////////////////////////////////////////// function hasParentInjector(parentLocation) { return parentLocation !== NO_PARENT_INJECTOR; } function getParentInjectorIndex(parentLocation) { ngDevMode && assertNumber(parentLocation, 'Number expected'); ngDevMode && assertNotEqual(parentLocation, -1, 'Not a valid state.'); const parentInjectorIndex = parentLocation & 32767 /* RelativeInjectorLocationFlags.InjectorIndexMask */; ngDevMode && assertGreaterThan(parentInjectorIndex, HEADER_OFFSET, 'Parent injector must be pointing past HEADER_OFFSET.'); return parentLocation & 32767 /* RelativeInjectorLocationFlags.InjectorIndexMask */; } function getParentInjectorViewOffset(parentLocation) { return parentLocation >> 16 /* RelativeInjectorLocationFlags.ViewOffsetShift */; } /** * Unwraps a parent injector location number to find the view offset from the current injector, * then walks up the declaration view tree until the view is found that contains the parent * injector. * * @param location The location of the parent injector, which contains the view offset * @param startView The LView instance from which to start walking up the view tree * @returns The LView instance that contains the parent injector */ function getParentInjectorView(location, startView) { let viewOffset = getParentInjectorViewOffset(location); let parentView = startView; // For most cases, the parent injector can be found on the host node (e.g. for component // or container), but we must keep the loop here to support the rarer case of deeply nested // tags or inline views, where the parent injector might live many views // above the child injector. while (viewOffset > 0) { parentView = parentView[DECLARATION_VIEW]; viewOffset--; } return parentView; } /** * Defines if the call to `inject` should include `viewProviders` in its resolution. * * This is set to true when we try to instantiate a component. This value is reset in * `getNodeInjectable` to a value which matches the declaration location of the token about to be * instantiated. This is done so that if we are injecting a token which was declared outside of * `viewProviders` we don't accidentally pull `viewProviders` in. * * Example: * * ``` * @Injectable() * class MyService { * constructor(public value: String) {} * } * * @Component({ * providers: [ * MyService, * {provide: String, value: 'providers' } * ] * viewProviders: [ * {provide: String, value: 'viewProviders'} * ] * }) * class MyComponent { * constructor(myService: MyService, value: String) { * // We expect that Component can see into `viewProviders`. * expect(value).toEqual('viewProviders'); * // `MyService` was not declared in `viewProviders` hence it can't see it. * expect(myService.value).toEqual('providers'); * } * } * * ``` */ let includeViewProviders = true; function setIncludeViewProviders(v) { const oldValue = includeViewProviders; includeViewProviders = v; return oldValue; } /** * The number of slots in each bloom filter (used by DI). The larger this number, the fewer * directives that will share slots, and thus, the fewer false positives when checking for * the existence of a directive. */ const BLOOM_SIZE = 256; const BLOOM_MASK = BLOOM_SIZE - 1; /** * The number of bits that is represented by a single bloom bucket. JS bit operations are 32 bits, * so each bucket represents 32 distinct tokens which accounts for log2(32) = 5 bits of a bloom hash * number. */ const BLOOM_BUCKET_BITS = 5; /** Counter used to generate unique IDs for directives. */ let nextNgElementId = 0; /** Value used when something wasn't found by an injector. */ const NOT_FOUND = {}; /** * Registers this directive as present in its node's injector by flipping the directive's * corresponding bit in the injector's bloom filter. * * @param injectorIndex The index of the node injector where this token should be registered * @param tView The TView for the injector's bloom filters * @param type The directive token to register */ function bloomAdd(injectorIndex, tView, type) { ngDevMode && assertEqual(tView.firstCreatePass, true, 'expected firstCreatePass to be true'); let id; if (typeof type === 'string') { id = type.charCodeAt(0) || 0; } else if (type.hasOwnProperty(NG_ELEMENT_ID)) { id = type[NG_ELEMENT_ID]; } // Set a unique ID on the directive type, so if something tries to inject the directive, // we can easily retrieve the ID and hash it into the bloom bit that should be checked. if (id == null) { id = type[NG_ELEMENT_ID] = nextNgElementId++; } // We only have BLOOM_SIZE (256) slots in our bloom filter (8 buckets * 32 bits each), // so all unique IDs must be modulo-ed into a number from 0 - 255 to fit into the filter. const bloomHash = id & BLOOM_MASK; // Create a mask that targets the specific bit associated with the directive. // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding // to bit positions 0 - 31 in a 32 bit integer. const mask = 1 << bloomHash; // Each bloom bucket in `tData` represents `BLOOM_BUCKET_BITS` number of bits of `bloomHash`. // Any bits in `bloomHash` beyond `BLOOM_BUCKET_BITS` indicate the bucket offset that the mask // should be written to. tView.data[injectorIndex + (bloomHash >> BLOOM_BUCKET_BITS)] |= mask; } /** * Creates (or gets an existing) injector for a given element or container. * * @param tNode for which an injector should be retrieved / created. * @param lView View where the node is stored * @returns Node injector */ function getOrCreateNodeInjectorForNode(tNode, lView) { const existingInjectorIndex = getInjectorIndex(tNode, lView); if (existingInjectorIndex !== -1) { return existingInjectorIndex; } const tView = lView[TVIEW]; if (tView.firstCreatePass) { tNode.injectorIndex = lView.length; insertBloom(tView.data, tNode); // foundation for node bloom insertBloom(lView, null); // foundation for cumulative bloom insertBloom(tView.blueprint, null); } const parentLoc = getParentInjectorLocation(tNode, lView); const injectorIndex = tNode.injectorIndex; // If a parent injector can't be found, its location is set to -1. // In that case, we don't need to set up a cumulative bloom if (hasParentInjector(parentLoc)) { const parentIndex = getParentInjectorIndex(parentLoc); const parentLView = getParentInjectorView(parentLoc, lView); const parentData = parentLView[TVIEW].data; // Creates a cumulative bloom filter that merges the parent's bloom filter // and its own cumulative bloom (which contains tokens for all ancestors) for (let i = 0; i < 8 /* NodeInjectorOffset.BLOOM_SIZE */; i++) { lView[injectorIndex + i] = parentLView[parentIndex + i] | parentData[parentIndex + i]; } } lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */] = parentLoc; return injectorIndex; } function insertBloom(arr, footer) { arr.push(0, 0, 0, 0, 0, 0, 0, 0, footer); } function getInjectorIndex(tNode, lView) { if (tNode.injectorIndex === -1 || // If the injector index is the same as its parent's injector index, then the index has been // copied down from the parent node. No injector has been created yet on this node. (tNode.parent && tNode.parent.injectorIndex === tNode.injectorIndex) || // After the first template pass, the injector index might exist but the parent values // might not have been calculated yet for this instance lView[tNode.injectorIndex + 8 /* NodeInjectorOffset.PARENT */] === null) { return -1; } else { ngDevMode && assertIndexInRange(lView, tNode.injectorIndex); return tNode.injectorIndex; } } /** * Finds the index of the parent injector, with a view offset if applicable. Used to set the * parent injector initially. * * @returns Returns a number that is the combination of the number of LViews that we have to go up * to find the LView containing the parent inject AND the index of the injector within that LView. */ function getParentInjectorLocation(tNode, lView) { if (tNode.parent && tNode.parent.injectorIndex !== -1) { // If we have a parent `TNode` and there is an injector associated with it we are done, because // the parent injector is within the current `LView`. return tNode.parent.injectorIndex; // ViewOffset is 0 } // When parent injector location is computed it may be outside of the current view. (ie it could // be pointing to a declared parent location). This variable stores number of declaration parents // we need to walk up in order to find the parent injector location. let declarationViewOffset = 0; let parentTNode = null; let lViewCursor = lView; // The parent injector is not in the current `LView`. We will have to walk the declared parent // `LView` hierarchy and look for it. If we walk of the top, that means that there is no parent // `NodeInjector`. while (lViewCursor !== null) { parentTNode = getTNodeFromLView(lViewCursor); if (parentTNode === null) { // If we have no parent, than we are done. return NO_PARENT_INJECTOR; } ngDevMode && parentTNode && assertTNodeForLView(parentTNode, lViewCursor[DECLARATION_VIEW]); // Every iteration of the loop requires that we go to the declared parent. declarationViewOffset++; lViewCursor = lViewCursor[DECLARATION_VIEW]; if (parentTNode.injectorIndex !== -1) { // We found a NodeInjector which points to something. return (parentTNode.injectorIndex | (declarationViewOffset << 16 /* RelativeInjectorLocationFlags.ViewOffsetShift */)); } } return NO_PARENT_INJECTOR; } /** * Makes a type or an injection token public to the DI system by adding it to an * injector's bloom filter. * * @param di The node injector in which a directive will be added * @param token The type or the injection token to be made public */ function diPublicInInjector(injectorIndex, tView, token) { bloomAdd(injectorIndex, tView, token); } /** * Inject static attribute value into directive constructor. * * This method is used with `factory` functions which are generated as part of * `defineDirective` or `defineComponent`. The method retrieves the static value * of an attribute. (Dynamic attributes are not supported since they are not resolved * at the time of injection and can change over time.) * * # Example * Given: * ``` * @Component(...) * class MyComponent { * constructor(@Attribute('title') title: string) { ... } * } * ``` * When instantiated with * ``` * * ``` * * Then factory method generated is: * ``` * MyComponent.ɵcmp = defineComponent({ * factory: () => new MyComponent(injectAttribute('title')) * ... * }) * ``` * * @publicApi */ function injectAttributeImpl(tNode, attrNameToInject) { ngDevMode && assertTNodeType(tNode, 12 /* TNodeType.AnyContainer */ | 3 /* TNodeType.AnyRNode */); ngDevMode && assertDefined(tNode, 'expecting tNode'); if (attrNameToInject === 'class') { return tNode.classes; } if (attrNameToInject === 'style') { return tNode.styles; } const attrs = tNode.attrs; if (attrs) { const attrsLength = attrs.length; let i = 0; while (i < attrsLength) { const value = attrs[i]; // If we hit a `Bindings` or `Template` marker then we are done. if (isNameOnlyAttributeMarker(value)) break; // Skip namespaced attributes if (value === 0 /* AttributeMarker.NamespaceURI */) { // we skip the next two values // as namespaced attributes looks like // [..., AttributeMarker.NamespaceURI, 'http://someuri.com/test', 'test:exist', // 'existValue', ...] i = i + 2; } else if (typeof value === 'number') { // Skip to the first value of the marked attribute. i++; while (i < attrsLength && typeof attrs[i] === 'string') { i++; } } else if (value === attrNameToInject) { return attrs[i + 1]; } else { i = i + 2; } } } return null; } function notFoundValueOrThrow(notFoundValue, token, flags) { if ((flags & InjectFlags.Optional) || notFoundValue !== undefined) { return notFoundValue; } else { throwProviderNotFoundError(token, 'NodeInjector'); } } /** * Returns the value associated to the given token from the ModuleInjector or throws exception * * @param lView The `LView` that contains the `tNode` * @param token The token to look for * @param flags Injection flags * @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional` * @returns the value from the injector or throws an exception */ function lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue) { if ((flags & InjectFlags.Optional) && notFoundValue === undefined) { // This must be set or the NullInjector will throw for optional deps notFoundValue = null; } if ((flags & (InjectFlags.Self | InjectFlags.Host)) === 0) { const moduleInjector = lView[INJECTOR$1]; // switch to `injectInjectorOnly` implementation for module injector, since module injector // should not have access to Component/Directive DI scope (that may happen through // `directiveInject` implementation) const previousInjectImplementation = setInjectImplementation(undefined); try { if (moduleInjector) { return moduleInjector.get(token, notFoundValue, flags & InjectFlags.Optional); } else { return injectRootLimpMode(token, notFoundValue, flags & InjectFlags.Optional); } } finally { setInjectImplementation(previousInjectImplementation); } } return notFoundValueOrThrow(notFoundValue, token, flags); } /** * Returns the value associated to the given token from the NodeInjectors => ModuleInjector. * * Look for the injector providing the token by walking up the node injector tree and then * the module injector tree. * * This function patches `token` with `__NG_ELEMENT_ID__` which contains the id for the bloom * filter. `-1` is reserved for injecting `Injector` (implemented by `NodeInjector`) * * @param tNode The Node where the search for the injector should start * @param lView The `LView` that contains the `tNode` * @param token The token to look for * @param flags Injection flags * @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional` * @returns the value from the injector, `null` when not found, or `notFoundValue` if provided */ function getOrCreateInjectable(tNode, lView, token, flags = InjectFlags.Default, notFoundValue) { if (tNode !== null) { // If the view or any of its ancestors have an embedded // view injector, we have to look it up there first. if (lView[FLAGS] & 2048 /* LViewFlags.HasEmbeddedViewInjector */ && // The token must be present on the current node injector when the `Self` // flag is set, so the lookup on embedded view injector(s) can be skipped. !(flags & InjectFlags.Self)) { const embeddedInjectorValue = lookupTokenUsingEmbeddedInjector(tNode, lView, token, flags, NOT_FOUND); if (embeddedInjectorValue !== NOT_FOUND) { return embeddedInjectorValue; } } // Otherwise try the node injector. const value = lookupTokenUsingNodeInjector(tNode, lView, token, flags, NOT_FOUND); if (value !== NOT_FOUND) { return value; } } // Finally, fall back to the module injector. return lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue); } /** * Returns the value associated to the given token from the node injector. * * @param tNode The Node where the search for the injector should start * @param lView The `LView` that contains the `tNode` * @param token The token to look for * @param flags Injection flags * @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional` * @returns the value from the injector, `null` when not found, or `notFoundValue` if provided */ function lookupTokenUsingNodeInjector(tNode, lView, token, flags, notFoundValue) { const bloomHash = bloomHashBitOrFactory(token); // If the ID stored here is a function, this is a special object like ElementRef or TemplateRef // so just call the factory function to create it. if (typeof bloomHash === 'function') { if (!enterDI(lView, tNode, flags)) { // Failed to enter DI, try module injector instead. If a token is injected with the @Host // flag, the module injector is not searched for that token in Ivy. return (flags & InjectFlags.Host) ? notFoundValueOrThrow(notFoundValue, token, flags) : lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue); } try { const value = bloomHash(flags); if (value == null && !(flags & InjectFlags.Optional)) { throwProviderNotFoundError(token); } else { return value; } } finally { leaveDI(); } } else if (typeof bloomHash === 'number') { // A reference to the previous injector TView that was found while climbing the element // injector tree. This is used to know if viewProviders can be accessed on the current // injector. let previousTView = null; let injectorIndex = getInjectorIndex(tNode, lView); let parentLocation = NO_PARENT_INJECTOR; let hostTElementNode = flags & InjectFlags.Host ? lView[DECLARATION_COMPONENT_VIEW][T_HOST] : null; // If we should skip this injector, or if there is no injector on this node, start by // searching the parent injector. if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) { parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) : lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */]; if (parentLocation === NO_PARENT_INJECTOR || !shouldSearchParent(flags, false)) { injectorIndex = -1; } else { previousTView = lView[TVIEW]; injectorIndex = getParentInjectorIndex(parentLocation); lView = getParentInjectorView(parentLocation, lView); } } // Traverse up the injector tree until we find a potential match or until we know there // *isn't* a match. while (injectorIndex !== -1) { ngDevMode && assertNodeInjector(lView, injectorIndex); // Check the current injector. If it matches, see if it contains token. const tView = lView[TVIEW]; ngDevMode && assertTNodeForLView(tView.data[injectorIndex + 8 /* NodeInjectorOffset.TNODE */], lView); if (bloomHasToken(bloomHash, injectorIndex, tView.data)) { // At this point, we have an injector which *may* contain the token, so we step through // the providers and directives associated with the injector's corresponding node to get // the instance. const instance = searchTokensOnInjector(injectorIndex, lView, token, previousTView, flags, hostTElementNode); if (instance !== NOT_FOUND) { return instance; } } parentLocation = lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */]; if (parentLocation !== NO_PARENT_INJECTOR && shouldSearchParent(flags, lView[TVIEW].data[injectorIndex + 8 /* NodeInjectorOffset.TNODE */] === hostTElementNode) && bloomHasToken(bloomHash, injectorIndex, lView)) { // The def wasn't found anywhere on this node, so it was a false positive. // Traverse up the tree and continue searching. previousTView = tView; injectorIndex = getParentInjectorIndex(parentLocation); lView = getParentInjectorView(parentLocation, lView); } else { // If we should not search parent OR If the ancestor bloom filter value does not have the // bit corresponding to the directive we can give up on traversing up to find the specific // injector. injectorIndex = -1; } } } return notFoundValue; } function searchTokensOnInjector(injectorIndex, lView, token, previousTView, flags, hostTElementNode) { const currentTView = lView[TVIEW]; const tNode = currentTView.data[injectorIndex + 8 /* NodeInjectorOffset.TNODE */]; // First, we need to determine if view providers can be accessed by the starting element. // There are two possibilities const canAccessViewProviders = previousTView == null ? // 1) This is the first invocation `previousTView == null` which means that we are at the // `TNode` of where injector is starting to look. In such a case the only time we are allowed // to look into the ViewProviders is if: // - we are on a component // - AND the injector set `includeViewProviders` to true (implying that the token can see // ViewProviders because it is the Component or a Service which itself was declared in // ViewProviders) (isComponentHost(tNode) && includeViewProviders) : // 2) `previousTView != null` which means that we are now walking across the parent nodes. // In such a case we are only allowed to look into the ViewProviders if: // - We just crossed from child View to Parent View `previousTView != currentTView` // - AND the parent TNode is an Element. // This means that we just came from the Component's View and therefore are allowed to see // into the ViewProviders. (previousTView != currentTView && ((tNode.type & 3 /* TNodeType.AnyRNode */) !== 0)); // This special case happens when there is a @host on the inject and when we are searching // on the host element node. const isHostSpecialCase = (flags & InjectFlags.Host) && hostTElementNode === tNode; const injectableIdx = locateDirectiveOrProvider(tNode, currentTView, token, canAccessViewProviders, isHostSpecialCase); if (injectableIdx !== null) { return getNodeInjectable(lView, currentTView, injectableIdx, tNode); } else { return NOT_FOUND; } } /** * Searches for the given token among the node's directives and providers. * * @param tNode TNode on which directives are present. * @param tView The tView we are currently processing * @param token Provider token or type of a directive to look for. * @param canAccessViewProviders Whether view providers should be considered. * @param isHostSpecialCase Whether the host special case applies. * @returns Index of a found directive or provider, or null when none found. */ function locateDirectiveOrProvider(tNode, tView, token, canAccessViewProviders, isHostSpecialCase) { const nodeProviderIndexes = tNode.providerIndexes; const tInjectables = tView.data; const injectablesStart = nodeProviderIndexes & 1048575 /* TNodeProviderIndexes.ProvidersStartIndexMask */; const directivesStart = tNode.directiveStart; const directiveEnd = tNode.directiveEnd; const cptViewProvidersCount = nodeProviderIndexes >> 20 /* TNodeProviderIndexes.CptViewProvidersCountShift */; const startingIndex = canAccessViewProviders ? injectablesStart : injectablesStart + cptViewProvidersCount; // When the host special case applies, only the viewProviders and the component are visible const endIndex = isHostSpecialCase ? injectablesStart + cptViewProvidersCount : directiveEnd; for (let i = startingIndex; i < endIndex; i++) { const providerTokenOrDef = tInjectables[i]; if (i < directivesStart && token === providerTokenOrDef || i >= directivesStart && providerTokenOrDef.type === token) { return i; } } if (isHostSpecialCase) { const dirDef = tInjectables[directivesStart]; if (dirDef && isComponentDef(dirDef) && dirDef.type === token) { return directivesStart; } } return null; } /** * Retrieve or instantiate the injectable from the `LView` at particular `index`. * * This function checks to see if the value has already been instantiated and if so returns the * cached `injectable`. Otherwise if it detects that the value is still a factory it * instantiates the `injectable` and caches the value. */ function getNodeInjectable(lView, tView, index, tNode) { let value = lView[index]; const tData = tView.data; if (isFactory(value)) { const factory = value; if (factory.resolving) { throwCyclicDependencyError(stringifyForError(tData[index])); } const previousIncludeViewProviders = setIncludeViewProviders(factory.canSeeViewProviders); factory.resolving = true; const previousInjectImplementation = factory.injectImpl ? setInjectImplementation(factory.injectImpl) : null; const success = enterDI(lView, tNode, InjectFlags.Default); ngDevMode && assertEqual(success, true, 'Because flags do not contain \`SkipSelf\' we expect this to always succeed.'); try { value = lView[index] = factory.factory(undefined, tData, lView, tNode); // This code path is hit for both directives and providers. // For perf reasons, we want to avoid searching for hooks on providers. // It does no harm to try (the hooks just won't exist), but the extra // checks are unnecessary and this is a hot path. So we check to see // if the index of the dependency is in the directive range for this // tNode. If it's not, we know it's a provider and skip hook registration. if (tView.firstCreatePass && index >= tNode.directiveStart) { ngDevMode && assertDirectiveDef(tData[index]); registerPreOrderHooks(index, tData[index], tView); } } finally { previousInjectImplementation !== null && setInjectImplementation(previousInjectImplementation); setIncludeViewProviders(previousIncludeViewProviders); factory.resolving = false; leaveDI(); } } return value; } /** * Returns the bit in an injector's bloom filter that should be used to determine whether or not * the directive might be provided by the injector. * * When a directive is public, it is added to the bloom filter and given a unique ID that can be * retrieved on the Type. When the directive isn't public or the token is not a directive `null` * is returned as the node injector can not possibly provide that token. * * @param token the injection token * @returns the matching bit to check in the bloom filter or `null` if the token is not known. * When the returned value is negative then it represents special values such as `Injector`. */ function bloomHashBitOrFactory(token) { ngDevMode && assertDefined(token, 'token must be defined'); if (typeof token === 'string') { return token.charCodeAt(0) || 0; } const tokenId = // First check with `hasOwnProperty` so we don't get an inherited ID. token.hasOwnProperty(NG_ELEMENT_ID) ? token[NG_ELEMENT_ID] : undefined; // Negative token IDs are used for special objects such as `Injector` if (typeof tokenId === 'number') { if (tokenId >= 0) { return tokenId & BLOOM_MASK; } else { ngDevMode && assertEqual(tokenId, -1 /* InjectorMarkers.Injector */, 'Expecting to get Special Injector Id'); return createNodeInjector; } } else { return tokenId; } } function bloomHasToken(bloomHash, injectorIndex, injectorView) { // Create a mask that targets the specific bit associated with the directive we're looking for. // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding // to bit positions 0 - 31 in a 32 bit integer. const mask = 1 << bloomHash; // Each bloom bucket in `injectorView` represents `BLOOM_BUCKET_BITS` number of bits of // `bloomHash`. Any bits in `bloomHash` beyond `BLOOM_BUCKET_BITS` indicate the bucket offset // that should be used. const value = injectorView[injectorIndex + (bloomHash >> BLOOM_BUCKET_BITS)]; // If the bloom filter value has the bit corresponding to the directive's bloomBit flipped on, // this injector is a potential match. return !!(value & mask); } /** Returns true if flags prevent parent injector from being searched for tokens */ function shouldSearchParent(flags, isFirstHostTNode) { return !(flags & InjectFlags.Self) && !(flags & InjectFlags.Host && isFirstHostTNode); } class NodeInjector { constructor(_tNode, _lView) { this._tNode = _tNode; this._lView = _lView; } get(token, notFoundValue, flags) { return getOrCreateInjectable(this._tNode, this._lView, token, convertToBitFlags(flags), notFoundValue); } } /** Creates a `NodeInjector` for the current node. */ function createNodeInjector() { return new NodeInjector(getCurrentTNode(), getLView()); } /** * @codeGenApi */ function ɵɵgetInheritedFactory(type) { return noSideEffects(() => { const ownConstructor = type.prototype.constructor; const ownFactory = ownConstructor[NG_FACTORY_DEF] || getFactoryOf(ownConstructor); const objectPrototype = Object.prototype; let parent = Object.getPrototypeOf(type.prototype).constructor; // Go up the prototype until we hit `Object`. while (parent && parent !== objectPrototype) { const factory = parent[NG_FACTORY_DEF] || getFactoryOf(parent); // If we hit something that has a factory and the factory isn't the same as the type, // we've found the inherited factory. Note the check that the factory isn't the type's // own factory is redundant in most cases, but if the user has custom decorators on the // class, this lookup will start one level down in the prototype chain, causing us to // find the own factory first and potentially triggering an infinite loop downstream. if (factory && factory !== ownFactory) { return factory; } parent = Object.getPrototypeOf(parent); } // There is no factory defined. Either this was improper usage of inheritance // (no Angular decorator on the superclass) or there is no constructor at all // in the inheritance chain. Since the two cases cannot be distinguished, the // latter has to be assumed. return t => new t(); }); } function getFactoryOf(type) { if (isForwardRef(type)) { return () => { const factory = getFactoryOf(resolveForwardRef(type)); return factory && factory(); }; } return getFactoryDef(type); } /** * Returns a value from the closest embedded or node injector. * * @param tNode The Node where the search for the injector should start * @param lView The `LView` that contains the `tNode` * @param token The token to look for * @param flags Injection flags * @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional` * @returns the value from the injector, `null` when not found, or `notFoundValue` if provided */ function lookupTokenUsingEmbeddedInjector(tNode, lView, token, flags, notFoundValue) { let currentTNode = tNode; let currentLView = lView; // When an LView with an embedded view injector is inserted, it'll likely be interlaced with // nodes who may have injectors (e.g. node injector -> embedded view injector -> node injector). // Since the bloom filters for the node injectors have already been constructed and we don't // have a way of extracting the records from an injector, the only way to maintain the correct // hierarchy when resolving the value is to walk it node-by-node while attempting to resolve // the token at each level. while (currentTNode !== null && currentLView !== null && (currentLView[FLAGS] & 2048 /* LViewFlags.HasEmbeddedViewInjector */) && !(currentLView[FLAGS] & 512 /* LViewFlags.IsRoot */)) { ngDevMode && assertTNodeForLView(currentTNode, currentLView); // Note that this lookup on the node injector is using the `Self` flag, because // we don't want the node injector to look at any parent injectors since we // may hit the embedded view injector first. const nodeInjectorValue = lookupTokenUsingNodeInjector(currentTNode, currentLView, token, flags | InjectFlags.Self, NOT_FOUND); if (nodeInjectorValue !== NOT_FOUND) { return nodeInjectorValue; } // Has an explicit type due to a TS bug: https://github.com/microsoft/TypeScript/issues/33191 let parentTNode = currentTNode.parent; // `TNode.parent` includes the parent within the current view only. If it doesn't exist, // it means that we've hit the view boundary and we need to go up to the next view. if (!parentTNode) { // Before we go to the next LView, check if the token exists on the current embedded injector. const embeddedViewInjector = currentLView[EMBEDDED_VIEW_INJECTOR]; if (embeddedViewInjector) { const embeddedViewInjectorValue = embeddedViewInjector.get(token, NOT_FOUND, flags); if (embeddedViewInjectorValue !== NOT_FOUND) { return embeddedViewInjectorValue; } } // Otherwise keep going up the tree. parentTNode = getTNodeFromLView(currentLView); currentLView = currentLView[DECLARATION_VIEW]; } currentTNode = parentTNode; } return notFoundValue; } /** Gets the TNode associated with an LView inside of the declaration view. */ function getTNodeFromLView(lView) { const tView = lView[TVIEW]; const tViewType = tView.type; // The parent pointer differs based on `TView.type`. if (tViewType === 2 /* TViewType.Embedded */) { ngDevMode && assertDefined(tView.declTNode, 'Embedded TNodes should have declaration parents.'); return tView.declTNode; } else if (tViewType === 1 /* TViewType.Component */) { // Components don't have `TView.declTNode` because each instance of component could be // inserted in different location, hence `TView.declTNode` is meaningless. return lView[T_HOST]; } return null; } /** * Facade for the attribute injection from DI. * * @codeGenApi */ function ɵɵinjectAttribute(attrNameToInject) { return injectAttributeImpl(getCurrentTNode(), attrNameToInject); } const ANNOTATIONS = '__annotations__'; const PARAMETERS = '__parameters__'; const PROP_METADATA = '__prop__metadata__'; /** * @suppress {globalThis} */ function makeDecorator(name, props, parentClass, additionalProcessing, typeFn) { return noSideEffects(() => { const metaCtor = makeMetadataCtor(props); function DecoratorFactory(...args) { if (this instanceof DecoratorFactory) { metaCtor.call(this, ...args); return this; } const annotationInstance = new DecoratorFactory(...args); return function TypeDecorator(cls) { if (typeFn) typeFn(cls, ...args); // Use of Object.defineProperty is important since it creates non-enumerable property which // prevents the property is copied during subclassing. const annotations = cls.hasOwnProperty(ANNOTATIONS) ? cls[ANNOTATIONS] : Object.defineProperty(cls, ANNOTATIONS, { value: [] })[ANNOTATIONS]; annotations.push(annotationInstance); if (additionalProcessing) additionalProcessing(cls); return cls; }; } if (parentClass) { DecoratorFactory.prototype = Object.create(parentClass.prototype); } DecoratorFactory.prototype.ngMetadataName = name; DecoratorFactory.annotationCls = DecoratorFactory; return DecoratorFactory; }); } function makeMetadataCtor(props) { return function ctor(...args) { if (props) { const values = props(...args); for (const propName in values) { this[propName] = values[propName]; } } }; } function makeParamDecorator(name, props, parentClass) { return noSideEffects(() => { const metaCtor = makeMetadataCtor(props); function ParamDecoratorFactory(...args) { if (this instanceof ParamDecoratorFactory) { metaCtor.apply(this, args); return this; } const annotationInstance = new ParamDecoratorFactory(...args); ParamDecorator.annotation = annotationInstance; return ParamDecorator; function ParamDecorator(cls, unusedKey, index) { // Use of Object.defineProperty is important since it creates non-enumerable property which // prevents the property is copied during subclassing. const parameters = cls.hasOwnProperty(PARAMETERS) ? cls[PARAMETERS] : Object.defineProperty(cls, PARAMETERS, { value: [] })[PARAMETERS]; // there might be gaps if some in between parameters do not have annotations. // we pad with nulls. while (parameters.length <= index) { parameters.push(null); } (parameters[index] = parameters[index] || []).push(annotationInstance); return cls; } } if (parentClass) { ParamDecoratorFactory.prototype = Object.create(parentClass.prototype); } ParamDecoratorFactory.prototype.ngMetadataName = name; ParamDecoratorFactory.annotationCls = ParamDecoratorFactory; return ParamDecoratorFactory; }); } function makePropDecorator(name, props, parentClass, additionalProcessing) { return noSideEffects(() => { const metaCtor = makeMetadataCtor(props); function PropDecoratorFactory(...args) { if (this instanceof PropDecoratorFactory) { metaCtor.apply(this, args); return this; } const decoratorInstance = new PropDecoratorFactory(...args); function PropDecorator(target, name) { // target is undefined with standard decorators. This case is not supported and will throw // if this decorator is used in JIT mode with standard decorators. if (target === undefined) { throw new Error('Standard Angular field decorators are not supported in JIT mode.'); } const constructor = target.constructor; // Use of Object.defineProperty is important because it creates a non-enumerable property // which prevents the property from being copied during subclassing. const meta = constructor.hasOwnProperty(PROP_METADATA) ? constructor[PROP_METADATA] : Object.defineProperty(constructor, PROP_METADATA, { value: {} })[PROP_METADATA]; meta[name] = meta.hasOwnProperty(name) && meta[name] || []; meta[name].unshift(decoratorInstance); if (additionalProcessing) additionalProcessing(target, name, ...args); } return PropDecorator; } if (parentClass) { PropDecoratorFactory.prototype = Object.create(parentClass.prototype); } PropDecoratorFactory.prototype.ngMetadataName = name; PropDecoratorFactory.annotationCls = PropDecoratorFactory; return PropDecoratorFactory; }); } /** * Attribute decorator and metadata. * * @Annotation * @publicApi */ const Attribute = exports('Attribute', makeParamDecorator('Attribute', (attributeName) => ({ attributeName, __NG_ELEMENT_ID__: () => ɵɵinjectAttribute(attributeName) }))); // Stores the default value of `emitDistinctChangesOnly` when the `emitDistinctChangesOnly` is not // explicitly set. const emitDistinctChangesOnlyDefaultValue = true; /** * Base class for query metadata. * * @see `ContentChildren`. * @see `ContentChild`. * @see `ViewChildren`. * @see `ViewChild`. * * @publicApi */ class Query { } exports('Query', Query); /** * ContentChildren decorator and metadata. * * * @Annotation * @publicApi */ const ContentChildren = exports('ContentChildren', makePropDecorator('ContentChildren', (selector, data = {}) => ({ selector, first: false, isViewQuery: false, descendants: false, emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue, ...data }), Query)); /** * ContentChild decorator and metadata. * * * @Annotation * * @publicApi */ const ContentChild = exports('ContentChild', makePropDecorator('ContentChild', (selector, data = {}) => ({ selector, first: true, isViewQuery: false, descendants: true, ...data }), Query)); /** * ViewChildren decorator and metadata. * * @Annotation * @publicApi */ const ViewChildren = exports('ViewChildren', makePropDecorator('ViewChildren', (selector, data = {}) => ({ selector, first: false, isViewQuery: true, descendants: true, emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue, ...data }), Query)); /** * ViewChild decorator and metadata. * * @Annotation * @publicApi */ const ViewChild = exports('ViewChild', makePropDecorator('ViewChild', (selector, data) => ({ selector, first: true, isViewQuery: true, descendants: true, ...data }), Query)); var FactoryTarget; exports('ɵɵFactoryTarget', FactoryTarget); (function (FactoryTarget) { FactoryTarget[FactoryTarget["Directive"] = 0] = "Directive"; FactoryTarget[FactoryTarget["Component"] = 1] = "Component"; FactoryTarget[FactoryTarget["Injectable"] = 2] = "Injectable"; FactoryTarget[FactoryTarget["Pipe"] = 3] = "Pipe"; FactoryTarget[FactoryTarget["NgModule"] = 4] = "NgModule"; })(FactoryTarget || (exports('ɵɵFactoryTarget', FactoryTarget = {}))); var R3TemplateDependencyKind; (function (R3TemplateDependencyKind) { R3TemplateDependencyKind[R3TemplateDependencyKind["Directive"] = 0] = "Directive"; R3TemplateDependencyKind[R3TemplateDependencyKind["Pipe"] = 1] = "Pipe"; R3TemplateDependencyKind[R3TemplateDependencyKind["NgModule"] = 2] = "NgModule"; })(R3TemplateDependencyKind || (R3TemplateDependencyKind = {})); var ViewEncapsulation; (function (ViewEncapsulation) { ViewEncapsulation[ViewEncapsulation["Emulated"] = 0] = "Emulated"; // Historically the 1 value was for `Native` encapsulation which has been removed as of v11. ViewEncapsulation[ViewEncapsulation["None"] = 2] = "None"; ViewEncapsulation[ViewEncapsulation["ShadowDom"] = 3] = "ShadowDom"; })(ViewEncapsulation || (ViewEncapsulation = {})); function getCompilerFacade(request) { const globalNg = _global['ng']; if (globalNg && globalNg.ɵcompilerFacade) { return globalNg.ɵcompilerFacade; } if (typeof ngDevMode === 'undefined' || ngDevMode) { // Log the type as an error so that a developer can easily navigate to the type from the // console. console.error(`JIT compilation failed for ${request.kind}`, request.type); let message = `The ${request.kind} '${request .type.name}' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available.\n\n`; if (request.usage === 1 /* JitCompilerUsage.PartialDeclaration */) { message += `The ${request.kind} is part of a library that has been partially compiled.\n`; message += `However, the Angular Linker has not processed the library such that JIT compilation is used as fallback.\n`; message += '\n'; message += `Ideally, the library is processed using the Angular Linker to become fully AOT compiled.\n`; } else { message += `JIT compilation is discouraged for production use-cases! Consider using AOT mode instead.\n`; } message += `Alternatively, the JIT compiler should be loaded by bootstrapping using '@angular/platform-browser-dynamic' or '@angular/platform-server',\n`; message += `or manually provide the compiler with 'import "@angular/compiler";' before bootstrapping.`; throw new Error(message); } else { throw new Error('JIT compiler unavailable'); } } /** * @description * * Represents a type that a Component or other object is instances of. * * An example of a `Type` is `MyCustomComponent` class, which in JavaScript is represented by * the `MyCustomComponent` constructor function. * * @publicApi */ const Type = exports('Type', Function); function isType(v) { return typeof v === 'function'; } /** * Determines if the contents of two arrays is identical * * @param a first array * @param b second array * @param identityAccessor Optional function for extracting stable object identity from a value in * the array. */ function arrayEquals(a, b, identityAccessor) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { let valueA = a[i]; let valueB = b[i]; if (identityAccessor) { valueA = identityAccessor(valueA); valueB = identityAccessor(valueB); } if (valueB !== valueA) { return false; } } return true; } /** * Flattens an array. */ function flatten(list) { return list.flat(Number.POSITIVE_INFINITY); } function deepForEach(input, fn) { input.forEach(value => Array.isArray(value) ? deepForEach(value, fn) : fn(value)); } function addToArray(arr, index, value) { // perf: array.push is faster than array.splice! if (index >= arr.length) { arr.push(value); } else { arr.splice(index, 0, value); } } function removeFromArray(arr, index) { // perf: array.pop is faster than array.splice! if (index >= arr.length - 1) { return arr.pop(); } else { return arr.splice(index, 1)[0]; } } function newArray(size, value) { const list = []; for (let i = 0; i < size; i++) { list.push(value); } return list; } /** * Same as `Array.splice2(index, 0, value1, value2)` but faster. * * `Array.splice()` is not fast because it has to allocate an array for the elements which were * removed. This causes memory pressure and slows down code when most of the time we don't * care about the deleted items array. * * @param array Array to splice. * @param index Index in array where the `value` should be added. * @param value1 Value to add to array. * @param value2 Value to add to array. */ function arrayInsert2(array, index, value1, value2) { ngDevMode && assertLessThanOrEqual(index, array.length, 'Can\'t insert past array end.'); let end = array.length; if (end == index) { // inserting at the end. array.push(value1, value2); } else if (end === 1) { // corner case when we have less items in array than we have items to insert. array.push(value2, array[0]); array[0] = value1; } else { end--; array.push(array[end - 1], array[end]); while (end > index) { const previousEnd = end - 2; array[end] = array[previousEnd]; end--; } array[index] = value1; array[index + 1] = value2; } } /** * Set a `value` for a `key`. * * @param keyValueArray to modify. * @param key The key to locate or create. * @param value The value to set for a `key`. * @returns index (always even) of where the value vas set. */ function keyValueArraySet(keyValueArray, key, value) { let index = keyValueArrayIndexOf(keyValueArray, key); if (index >= 0) { // if we found it set it. keyValueArray[index | 1] = value; } else { index = ~index; arrayInsert2(keyValueArray, index, key, value); } return index; } /** * Retrieve a `value` for a `key` (on `undefined` if not found.) * * @param keyValueArray to search. * @param key The key to locate. * @return The `value` stored at the `key` location or `undefined if not found. */ function keyValueArrayGet(keyValueArray, key) { const index = keyValueArrayIndexOf(keyValueArray, key); if (index >= 0) { // if we found it retrieve it. return keyValueArray[index | 1]; } return undefined; } /** * Retrieve a `key` index value in the array or `-1` if not found. * * @param keyValueArray to search. * @param key The key to locate. * @returns index of where the key is (or should have been.) * - positive (even) index if key found. * - negative index if key not found. (`~index` (even) to get the index where it should have * been inserted.) */ function keyValueArrayIndexOf(keyValueArray, key) { return _arrayIndexOfSorted(keyValueArray, key, 1); } /** * INTERNAL: Get an index of an `value` in a sorted `array` by grouping search by `shift`. * * NOTE: * - This uses binary search algorithm for fast removals. * * @param array A sorted array to binary search. * @param value The value to look for. * @param shift grouping shift. * - `0` means look at every location * - `1` means only look at every other (even) location (the odd locations are to be ignored as * they are values.) * @returns index of the value. * - positive index if value found. * - negative index if value not found. (`~index` to get the value where it should have been * inserted) */ function _arrayIndexOfSorted(array, value, shift) { ngDevMode && assertEqual(Array.isArray(array), true, 'Expecting an array'); let start = 0; let end = array.length >> shift; while (end !== start) { const middle = start + ((end - start) >> 1); // find the middle. const current = array[middle << shift]; if (value === current) { return (middle << shift); } else if (current > value) { end = middle; } else { start = middle + 1; // We already searched middle so make it non-inclusive by adding 1 } } return ~(end << shift); } /* * ######################### * Attention: These Regular expressions have to hold even if the code is minified! * ########################## */ /** * Regular expression that detects pass-through constructors for ES5 output. This Regex * intends to capture the common delegation pattern emitted by TypeScript and Babel. Also * it intends to capture the pattern where existing constructors have been downleveled from * ES2015 to ES5 using TypeScript w/ downlevel iteration. e.g. * * ``` * function MyClass() { * var _this = _super.apply(this, arguments) || this; * ``` * * downleveled to ES5 with `downlevelIteration` for TypeScript < 4.2: * ``` * function MyClass() { * var _this = _super.apply(this, __spread(arguments)) || this; * ``` * * or downleveled to ES5 with `downlevelIteration` for TypeScript >= 4.2: * ``` * function MyClass() { * var _this = _super.apply(this, __spreadArray([], __read(arguments), false)) || this; * ``` * * More details can be found in: https://github.com/angular/angular/issues/38453. */ const ES5_DELEGATE_CTOR = /^function\s+\S+\(\)\s*{[\s\S]+\.apply\(this,\s*(arguments|(?:[^()]+\(\[\],)?[^()]+\(arguments\).*)\)/; /** Regular expression that detects ES2015 classes which extend from other classes. */ const ES2015_INHERITED_CLASS = /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{/; /** * Regular expression that detects ES2015 classes which extend from other classes and * have an explicit constructor defined. */ const ES2015_INHERITED_CLASS_WITH_CTOR = /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(/; /** * Regular expression that detects ES2015 classes which extend from other classes * and inherit a constructor. */ const ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR = /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(\)\s*{[^}]*super\(\.\.\.arguments\)/; /** * Determine whether a stringified type is a class which delegates its constructor * to its parent. * * This is not trivial since compiled code can actually contain a constructor function * even if the original source code did not. For instance, when the child class contains * an initialized instance property. */ function isDelegateCtor(typeStr) { return ES5_DELEGATE_CTOR.test(typeStr) || ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR.test(typeStr) || (ES2015_INHERITED_CLASS.test(typeStr) && !ES2015_INHERITED_CLASS_WITH_CTOR.test(typeStr)); } class ReflectionCapabilities { constructor(reflect) { this._reflect = reflect || _global['Reflect']; } factory(t) { return (...args) => new t(...args); } /** @internal */ _zipTypesAndAnnotations(paramTypes, paramAnnotations) { let result; if (typeof paramTypes === 'undefined') { result = newArray(paramAnnotations.length); } else { result = newArray(paramTypes.length); } for (let i = 0; i < result.length; i++) { // TS outputs Object for parameters without types, while Traceur omits // the annotations. For now we preserve the Traceur behavior to aid // migration, but this can be revisited. if (typeof paramTypes === 'undefined') { result[i] = []; } else if (paramTypes[i] && paramTypes[i] != Object) { result[i] = [paramTypes[i]]; } else { result[i] = []; } if (paramAnnotations && paramAnnotations[i] != null) { result[i] = result[i].concat(paramAnnotations[i]); } } return result; } _ownParameters(type, parentCtor) { const typeStr = type.toString(); // If we have no decorators, we only have function.length as metadata. // In that case, to detect whether a child class declared an own constructor or not, // we need to look inside of that constructor to check whether it is // just calling the parent. // This also helps to work around for https://github.com/Microsoft/TypeScript/issues/12439 // that sets 'design:paramtypes' to [] // if a class inherits from another class but has no ctor declared itself. if (isDelegateCtor(typeStr)) { return null; } // Prefer the direct API. if (type.parameters && type.parameters !== parentCtor.parameters) { return type.parameters; } // API of tsickle for lowering decorators to properties on the class. const tsickleCtorParams = type.ctorParameters; if (tsickleCtorParams && tsickleCtorParams !== parentCtor.ctorParameters) { // Newer tsickle uses a function closure // Retain the non-function case for compatibility with older tsickle const ctorParameters = typeof tsickleCtorParams === 'function' ? tsickleCtorParams() : tsickleCtorParams; const paramTypes = ctorParameters.map((ctorParam) => ctorParam && ctorParam.type); const paramAnnotations = ctorParameters.map((ctorParam) => ctorParam && convertTsickleDecoratorIntoMetadata(ctorParam.decorators)); return this._zipTypesAndAnnotations(paramTypes, paramAnnotations); } // API for metadata created by invoking the decorators. const paramAnnotations = type.hasOwnProperty(PARAMETERS) && type[PARAMETERS]; const paramTypes = this._reflect && this._reflect.getOwnMetadata && this._reflect.getOwnMetadata('design:paramtypes', type); if (paramTypes || paramAnnotations) { return this._zipTypesAndAnnotations(paramTypes, paramAnnotations); } // If a class has no decorators, at least create metadata // based on function.length. // Note: We know that this is a real constructor as we checked // the content of the constructor above. return newArray(type.length); } parameters(type) { // Note: only report metadata if we have at least one class decorator // to stay in sync with the static reflector. if (!isType(type)) { return []; } const parentCtor = getParentCtor(type); let parameters = this._ownParameters(type, parentCtor); if (!parameters && parentCtor !== Object) { parameters = this.parameters(parentCtor); } return parameters || []; } _ownAnnotations(typeOrFunc, parentCtor) { // Prefer the direct API. if (typeOrFunc.annotations && typeOrFunc.annotations !== parentCtor.annotations) { let annotations = typeOrFunc.annotations; if (typeof annotations === 'function' && annotations.annotations) { annotations = annotations.annotations; } return annotations; } // API of tsickle for lowering decorators to properties on the class. if (typeOrFunc.decorators && typeOrFunc.decorators !== parentCtor.decorators) { return convertTsickleDecoratorIntoMetadata(typeOrFunc.decorators); } // API for metadata created by invoking the decorators. if (typeOrFunc.hasOwnProperty(ANNOTATIONS)) { return typeOrFunc[ANNOTATIONS]; } return null; } annotations(typeOrFunc) { if (!isType(typeOrFunc)) { return []; } const parentCtor = getParentCtor(typeOrFunc); const ownAnnotations = this._ownAnnotations(typeOrFunc, parentCtor) || []; const parentAnnotations = parentCtor !== Object ? this.annotations(parentCtor) : []; return parentAnnotations.concat(ownAnnotations); } _ownPropMetadata(typeOrFunc, parentCtor) { // Prefer the direct API. if (typeOrFunc.propMetadata && typeOrFunc.propMetadata !== parentCtor.propMetadata) { let propMetadata = typeOrFunc.propMetadata; if (typeof propMetadata === 'function' && propMetadata.propMetadata) { propMetadata = propMetadata.propMetadata; } return propMetadata; } // API of tsickle for lowering decorators to properties on the class. if (typeOrFunc.propDecorators && typeOrFunc.propDecorators !== parentCtor.propDecorators) { const propDecorators = typeOrFunc.propDecorators; const propMetadata = {}; Object.keys(propDecorators).forEach(prop => { propMetadata[prop] = convertTsickleDecoratorIntoMetadata(propDecorators[prop]); }); return propMetadata; } // API for metadata created by invoking the decorators. if (typeOrFunc.hasOwnProperty(PROP_METADATA)) { return typeOrFunc[PROP_METADATA]; } return null; } propMetadata(typeOrFunc) { if (!isType(typeOrFunc)) { return {}; } const parentCtor = getParentCtor(typeOrFunc); const propMetadata = {}; if (parentCtor !== Object) { const parentPropMetadata = this.propMetadata(parentCtor); Object.keys(parentPropMetadata).forEach((propName) => { propMetadata[propName] = parentPropMetadata[propName]; }); } const ownPropMetadata = this._ownPropMetadata(typeOrFunc, parentCtor); if (ownPropMetadata) { Object.keys(ownPropMetadata).forEach((propName) => { const decorators = []; if (propMetadata.hasOwnProperty(propName)) { decorators.push(...propMetadata[propName]); } decorators.push(...ownPropMetadata[propName]); propMetadata[propName] = decorators; }); } return propMetadata; } ownPropMetadata(typeOrFunc) { if (!isType(typeOrFunc)) { return {}; } return this._ownPropMetadata(typeOrFunc, getParentCtor(typeOrFunc)) || {}; } hasLifecycleHook(type, lcProperty) { return type instanceof Type && lcProperty in type.prototype; } } exports('ɵReflectionCapabilities', ReflectionCapabilities); function convertTsickleDecoratorIntoMetadata(decoratorInvocations) { if (!decoratorInvocations) { return []; } return decoratorInvocations.map(decoratorInvocation => { const decoratorType = decoratorInvocation.type; const annotationCls = decoratorType.annotationCls; const annotationArgs = decoratorInvocation.args ? decoratorInvocation.args : []; return new annotationCls(...annotationArgs); }); } function getParentCtor(ctor) { const parentProto = ctor.prototype ? Object.getPrototypeOf(ctor.prototype) : null; const parentCtor = parentProto ? parentProto.constructor : null; // Note: We always use `Object` as the null value // to simplify checking later on. return parentCtor || Object; } /** * Inject decorator and metadata. * * @Annotation * @publicApi */ const Inject = exports('Inject', attachInjectFlag( // Disable tslint because `DecoratorFlags` is a const enum which gets inlined. // tslint:disable-next-line: no-toplevel-property-access makeParamDecorator('Inject', (token) => ({ token })), -1 /* DecoratorFlags.Inject */)); /** * Optional decorator and metadata. * * @Annotation * @publicApi */ const Optional = exports('Optional', // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. // tslint:disable-next-line: no-toplevel-property-access attachInjectFlag(makeParamDecorator('Optional'), 8 /* InternalInjectFlags.Optional */)); /** * Self decorator and metadata. * * @Annotation * @publicApi */ const Self = exports('Self', // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. // tslint:disable-next-line: no-toplevel-property-access attachInjectFlag(makeParamDecorator('Self'), 2 /* InternalInjectFlags.Self */)); /** * `SkipSelf` decorator and metadata. * * @Annotation * @publicApi */ const SkipSelf = exports('SkipSelf', // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. // tslint:disable-next-line: no-toplevel-property-access attachInjectFlag(makeParamDecorator('SkipSelf'), 4 /* InternalInjectFlags.SkipSelf */)); /** * Host decorator and metadata. * * @Annotation * @publicApi */ const Host = exports('Host', // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined. // tslint:disable-next-line: no-toplevel-property-access attachInjectFlag(makeParamDecorator('Host'), 1 /* InternalInjectFlags.Host */)); let _reflect = null; function getReflect() { return (_reflect = _reflect || new ReflectionCapabilities()); } function reflectDependencies(type) { return convertDependencies(getReflect().parameters(type)); } function convertDependencies(deps) { return deps.map(dep => reflectDependency(dep)); } function reflectDependency(dep) { const meta = { token: null, attribute: null, host: false, optional: false, self: false, skipSelf: false, }; if (Array.isArray(dep) && dep.length > 0) { for (let j = 0; j < dep.length; j++) { const param = dep[j]; if (param === undefined) { // param may be undefined if type of dep is not set by ngtsc continue; } const proto = Object.getPrototypeOf(param); if (param instanceof Optional || proto.ngMetadataName === 'Optional') { meta.optional = true; } else if (param instanceof SkipSelf || proto.ngMetadataName === 'SkipSelf') { meta.skipSelf = true; } else if (param instanceof Self || proto.ngMetadataName === 'Self') { meta.self = true; } else if (param instanceof Host || proto.ngMetadataName === 'Host') { meta.host = true; } else if (param instanceof Inject) { meta.token = param.token; } else if (param instanceof Attribute) { if (param.attributeName === undefined) { throw new RuntimeError(204 /* RuntimeErrorCode.INVALID_INJECTION_TOKEN */, ngDevMode && `Attribute name must be defined.`); } meta.attribute = param.attributeName; } else { meta.token = param; } } } else if (dep === undefined || (Array.isArray(dep) && dep.length === 0)) { meta.token = null; } else { meta.token = dep; } return meta; } /** * Used to resolve resource URLs on `@Component` when used with JIT compilation. * * Example: * ``` * @Component({ * selector: 'my-comp', * templateUrl: 'my-comp.html', // This requires asynchronous resolution * }) * class MyComponent{ * } * * // Calling `renderComponent` will fail because `renderComponent` is a synchronous process * // and `MyComponent`'s `@Component.templateUrl` needs to be resolved asynchronously. * * // Calling `resolveComponentResources()` will resolve `@Component.templateUrl` into * // `@Component.template`, which allows `renderComponent` to proceed in a synchronous manner. * * // Use browser's `fetch()` function as the default resource resolution strategy. * resolveComponentResources(fetch).then(() => { * // After resolution all URLs have been converted into `template` strings. * renderComponent(MyComponent); * }); * * ``` * * NOTE: In AOT the resolution happens during compilation, and so there should be no need * to call this method outside JIT mode. * * @param resourceResolver a function which is responsible for returning a `Promise` to the * contents of the resolved URL. Browser's `fetch()` method is a good default implementation. */ function resolveComponentResources(resourceResolver) { // Store all promises which are fetching the resources. const componentResolved = []; // Cache so that we don't fetch the same resource more than once. const urlMap = new Map(); function cachedResourceResolve(url) { let promise = urlMap.get(url); if (!promise) { const resp = resourceResolver(url); urlMap.set(url, promise = resp.then(unwrapResponse)); } return promise; } componentResourceResolutionQueue.forEach((component, type) => { const promises = []; if (component.templateUrl) { promises.push(cachedResourceResolve(component.templateUrl).then((template) => { component.template = template; })); } const styleUrls = component.styleUrls; const styles = component.styles || (component.styles = []); const styleOffset = component.styles.length; styleUrls && styleUrls.forEach((styleUrl, index) => { styles.push(''); // pre-allocate array. promises.push(cachedResourceResolve(styleUrl).then((style) => { styles[styleOffset + index] = style; styleUrls.splice(styleUrls.indexOf(styleUrl), 1); if (styleUrls.length == 0) { component.styleUrls = undefined; } })); }); const fullyResolved = Promise.all(promises).then(() => componentDefResolved(type)); componentResolved.push(fullyResolved); }); clearResolutionOfComponentResourcesQueue(); return Promise.all(componentResolved).then(() => undefined); } let componentResourceResolutionQueue = new Map(); // Track when existing ɵcmp for a Type is waiting on resources. const componentDefPendingResolution = new Set(); function maybeQueueResolutionOfComponentResources(type, metadata) { if (componentNeedsResolution(metadata)) { componentResourceResolutionQueue.set(type, metadata); componentDefPendingResolution.add(type); } } function componentNeedsResolution(component) { return !!((component.templateUrl && !component.hasOwnProperty('template')) || component.styleUrls && component.styleUrls.length); } function clearResolutionOfComponentResourcesQueue() { const old = componentResourceResolutionQueue; componentResourceResolutionQueue = new Map(); return old; } function isComponentResourceResolutionQueueEmpty() { return componentResourceResolutionQueue.size === 0; } function unwrapResponse(response) { return typeof response == 'string' ? response : response.text(); } function componentDefResolved(type) { componentDefPendingResolution.delete(type); } /** * Map of module-id to the corresponding NgModule. */ const modules = new Map(); /** * Whether to check for duplicate NgModule registrations. * * This can be disabled for testing. */ let checkForDuplicateNgModules = true; function assertSameOrNotExisting(id, type, incoming) { if (type && type !== incoming && checkForDuplicateNgModules) { throw new Error(`Duplicate module registered for ${id} - ${stringify(type)} vs ${stringify(type.name)}`); } } /** * Adds the given NgModule type to Angular's NgModule registry. * * This is generated as a side-effect of NgModule compilation. Note that the `id` is passed in * explicitly and not read from the NgModule definition. This is for two reasons: it avoids a * megamorphic read, and in JIT there's a chicken-and-egg problem where the NgModule may not be * fully resolved when it's registered. * * @codeGenApi */ function registerNgModuleType(ngModuleType, id) { const existing = modules.get(id) || null; assertSameOrNotExisting(id, existing, ngModuleType); modules.set(id, ngModuleType); } function getRegisteredNgModuleType(id) { return modules.get(id); } /** * Control whether the NgModule registration system enforces that each NgModule type registered has * a unique id. * * This is useful for testing as the NgModule registry cannot be properly reset between tests with * Angular's current API. */ function setAllowDuplicateNgModuleIdsForTest(allowDuplicates) { checkForDuplicateNgModules = !allowDuplicates; } /** * Defines a schema that allows an NgModule to contain the following: * - Non-Angular elements named with dash case (`-`). * - Element properties named with dash case (`-`). * Dash case is the naming convention for custom elements. * * @publicApi */ const CUSTOM_ELEMENTS_SCHEMA = exports('CUSTOM_ELEMENTS_SCHEMA', { name: 'custom-elements' }); /** * Defines a schema that allows any property on any element. * * This schema allows you to ignore the errors related to any unknown elements or properties in a * template. The usage of this schema is generally discouraged because it prevents useful validation * and may hide real errors in your template. Consider using the `CUSTOM_ELEMENTS_SCHEMA` instead. * * @publicApi */ const NO_ERRORS_SCHEMA = exports('NO_ERRORS_SCHEMA', { name: 'no-errors-schema' }); let shouldThrowErrorOnUnknownElement = false; /** * Sets a strict mode for JIT-compiled components to throw an error on unknown elements, * instead of just logging the error. * (for AOT-compiled ones this check happens at build time). */ function ɵsetUnknownElementStrictMode(shouldThrow) { shouldThrowErrorOnUnknownElement = shouldThrow; } /** * Gets the current value of the strict mode. */ function ɵgetUnknownElementStrictMode() { return shouldThrowErrorOnUnknownElement; } let shouldThrowErrorOnUnknownProperty = false; /** * Sets a strict mode for JIT-compiled components to throw an error on unknown properties, * instead of just logging the error. * (for AOT-compiled ones this check happens at build time). */ function ɵsetUnknownPropertyStrictMode(shouldThrow) { shouldThrowErrorOnUnknownProperty = shouldThrow; } /** * Gets the current value of the strict mode. */ function ɵgetUnknownPropertyStrictMode() { return shouldThrowErrorOnUnknownProperty; } /** * Validates that the element is known at runtime and produces * an error if it's not the case. * This check is relevant for JIT-compiled components (for AOT-compiled * ones this check happens at build time). * * The element is considered known if either: * - it's a known HTML element * - it's a known custom element * - the element matches any directive * - the element is allowed by one of the schemas * * @param element Element to validate * @param lView An `LView` that represents a current component that is being rendered * @param tagName Name of the tag to check * @param schemas Array of schemas * @param hasDirectives Boolean indicating that the element matches any directive */ function validateElementIsKnown(element, lView, tagName, schemas, hasDirectives) { // If `schemas` is set to `null`, that's an indication that this Component was compiled in AOT // mode where this check happens at compile time. In JIT mode, `schemas` is always present and // defined as an array (as an empty array in case `schemas` field is not defined) and we should // execute the check below. if (schemas === null) return; // If the element matches any directive, it's considered as valid. if (!hasDirectives && tagName !== null) { // The element is unknown if it's an instance of HTMLUnknownElement, or it isn't registered // as a custom element. Note that unknown elements with a dash in their name won't be instances // of HTMLUnknownElement in browsers that support web components. const isUnknown = // Note that we can't check for `typeof HTMLUnknownElement === 'function'` because // Domino doesn't expose HTMLUnknownElement globally. (typeof HTMLUnknownElement !== 'undefined' && HTMLUnknownElement && element instanceof HTMLUnknownElement) || (typeof customElements !== 'undefined' && tagName.indexOf('-') > -1 && !customElements.get(tagName)); if (isUnknown && !matchingSchemas(schemas, tagName)) { const isHostStandalone = isHostComponentStandalone(lView); const templateLocation = getTemplateLocationDetails(lView); const schemas = `'${isHostStandalone ? '@Component' : '@NgModule'}.schemas'`; let message = `'${tagName}' is not a known element${templateLocation}:\n`; message += `1. If '${tagName}' is an Angular component, then verify that it is ${isHostStandalone ? 'included in the \'@Component.imports\' of this component' : 'a part of an @NgModule where this component is declared'}.\n`; if (tagName && tagName.indexOf('-') > -1) { message += `2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the ${schemas} of this component to suppress this message.`; } else { message += `2. To allow any element add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`; } if (shouldThrowErrorOnUnknownElement) { throw new RuntimeError(304 /* RuntimeErrorCode.UNKNOWN_ELEMENT */, message); } else { console.error(formatRuntimeError(304 /* RuntimeErrorCode.UNKNOWN_ELEMENT */, message)); } } } } /** * Validates that the property of the element is known at runtime and returns * false if it's not the case. * This check is relevant for JIT-compiled components (for AOT-compiled * ones this check happens at build time). * * The property is considered known if either: * - it's a known property of the element * - the element is allowed by one of the schemas * - the property is used for animations * * @param element Element to validate * @param propName Name of the property to check * @param tagName Name of the tag hosting the property * @param schemas Array of schemas */ function isPropertyValid(element, propName, tagName, schemas) { // If `schemas` is set to `null`, that's an indication that this Component was compiled in AOT // mode where this check happens at compile time. In JIT mode, `schemas` is always present and // defined as an array (as an empty array in case `schemas` field is not defined) and we should // execute the check below. if (schemas === null) return true; // The property is considered valid if the element matches the schema, it exists on the element, // or it is synthetic. if (matchingSchemas(schemas, tagName) || propName in element || isAnimationProp(propName)) { return true; } // Note: `typeof Node` returns 'function' in most browsers, but is undefined with domino. return typeof Node === 'undefined' || Node === null || !(element instanceof Node); } /** * Logs or throws an error that a property is not supported on an element. * * @param propName Name of the invalid property * @param tagName Name of the tag hosting the property * @param nodeType Type of the node hosting the property * @param lView An `LView` that represents a current component */ function handleUnknownPropertyError(propName, tagName, nodeType, lView) { // Special-case a situation when a structural directive is applied to // an `` element, for example: ``. // In this case the compiler generates the `ɵɵtemplate` instruction with // the `null` as the tagName. The directive matching logic at runtime relies // on this effect (see `isInlineTemplate`), thus using the 'ng-template' as // a default value of the `tNode.value` is not feasible at this moment. if (!tagName && nodeType === 4 /* TNodeType.Container */) { tagName = 'ng-template'; } const isHostStandalone = isHostComponentStandalone(lView); const templateLocation = getTemplateLocationDetails(lView); let message = `Can't bind to '${propName}' since it isn't a known property of '${tagName}'${templateLocation}.`; const schemas = `'${isHostStandalone ? '@Component' : '@NgModule'}.schemas'`; const importLocation = isHostStandalone ? 'included in the \'@Component.imports\' of this component' : 'a part of an @NgModule where this component is declared'; if (KNOWN_CONTROL_FLOW_DIRECTIVES.has(propName)) { // Most likely this is a control flow directive (such as `*ngIf`) used in // a template, but the directive or the `CommonModule` is not imported. const correspondingImport = KNOWN_CONTROL_FLOW_DIRECTIVES.get(propName); message += `\nIf the '${propName}' is an Angular control flow directive, ` + `please make sure that either the '${correspondingImport}' directive or the 'CommonModule' is ${importLocation}.`; } else { // May be an Angular component, which is not imported/declared? message += `\n1. If '${tagName}' is an Angular component and it has the ` + `'${propName}' input, then verify that it is ${importLocation}.`; // May be a Web Component? if (tagName && tagName.indexOf('-') > -1) { message += `\n2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' ` + `to the ${schemas} of this component to suppress this message.`; message += `\n3. To allow any property add 'NO_ERRORS_SCHEMA' to ` + `the ${schemas} of this component.`; } else { // If it's expected, the error can be suppressed by the `NO_ERRORS_SCHEMA` schema. message += `\n2. To allow any property add 'NO_ERRORS_SCHEMA' to ` + `the ${schemas} of this component.`; } } reportUnknownPropertyError(message); } function reportUnknownPropertyError(message) { if (shouldThrowErrorOnUnknownProperty) { throw new RuntimeError(303 /* RuntimeErrorCode.UNKNOWN_BINDING */, message); } else { console.error(formatRuntimeError(303 /* RuntimeErrorCode.UNKNOWN_BINDING */, message)); } } /** * WARNING: this is a **dev-mode only** function (thus should always be guarded by the `ngDevMode`) * and must **not** be used in production bundles. The function makes megamorphic reads, which might * be too slow for production mode and also it relies on the constructor function being available. * * Gets a reference to the host component def (where a current component is declared). * * @param lView An `LView` that represents a current component that is being rendered. */ function getDeclarationComponentDef(lView) { !ngDevMode && throwError('Must never be called in production mode'); const declarationLView = lView[DECLARATION_COMPONENT_VIEW]; const context = declarationLView[CONTEXT]; // Unable to obtain a context. if (!context) return null; return context.constructor ? getComponentDef(context.constructor) : null; } /** * WARNING: this is a **dev-mode only** function (thus should always be guarded by the `ngDevMode`) * and must **not** be used in production bundles. The function makes megamorphic reads, which might * be too slow for production mode. * * Checks if the current component is declared inside of a standalone component template. * * @param lView An `LView` that represents a current component that is being rendered. */ function isHostComponentStandalone(lView) { !ngDevMode && throwError('Must never be called in production mode'); const componentDef = getDeclarationComponentDef(lView); // Treat host component as non-standalone if we can't obtain the def. return !!componentDef?.standalone; } /** * WARNING: this is a **dev-mode only** function (thus should always be guarded by the `ngDevMode`) * and must **not** be used in production bundles. The function makes megamorphic reads, which might * be too slow for production mode. * * Constructs a string describing the location of the host component template. The function is used * in dev mode to produce error messages. * * @param lView An `LView` that represents a current component that is being rendered. */ function getTemplateLocationDetails(lView) { !ngDevMode && throwError('Must never be called in production mode'); const hostComponentDef = getDeclarationComponentDef(lView); const componentClassName = hostComponentDef?.type?.name; return componentClassName ? ` (used in the '${componentClassName}' component template)` : ''; } /** * The set of known control flow directives and their corresponding imports. * We use this set to produce a more precises error message with a note * that the `CommonModule` should also be included. */ const KNOWN_CONTROL_FLOW_DIRECTIVES = new Map([ ['ngIf', 'NgIf'], ['ngFor', 'NgFor'], ['ngSwitchCase', 'NgSwitchCase'], ['ngSwitchDefault', 'NgSwitchDefault'] ]); /** * Returns true if the tag name is allowed by specified schemas. * @param schemas Array of schemas * @param tagName Name of the tag */ function matchingSchemas(schemas, tagName) { if (schemas !== null) { for (let i = 0; i < schemas.length; i++) { const schema = schemas[i]; if (schema === NO_ERRORS_SCHEMA || schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) { return true; } } } return false; } /** * The name of an attribute that can be added to the hydration boundary node * (component host node) to disable hydration for the content within that boundary. */ const SKIP_HYDRATION_ATTR_NAME = 'ngSkipHydration'; /** * Helper function to check if a given node has the 'ngSkipHydration' attribute */ function hasNgSkipHydrationAttr(tNode) { const SKIP_HYDRATION_ATTR_NAME_LOWER_CASE = SKIP_HYDRATION_ATTR_NAME.toLowerCase(); const attrs = tNode.mergedAttrs; if (attrs === null) return false; // only ever look at the attribute name and skip the values for (let i = 0; i < attrs.length; i += 2) { const value = attrs[i]; // This is a marker, which means that the static attributes section is over, // so we can exit early. if (typeof value === 'number') return false; if (typeof value === 'string' && value.toLowerCase() === SKIP_HYDRATION_ATTR_NAME_LOWER_CASE) { return true; } } return false; } /** * Checks whether a TNode has a flag to indicate that it's a part of * a skip hydration block. */ function hasInSkipHydrationBlockFlag(tNode) { return (tNode.flags & 128 /* TNodeFlags.inSkipHydrationBlock */) === 128 /* TNodeFlags.inSkipHydrationBlock */; } /** * Helper function that determines if a given node is within a skip hydration block * by navigating up the TNode tree to see if any parent nodes have skip hydration * attribute. * * TODO(akushnir): this function should contain the logic of `hasInSkipHydrationBlockFlag`, * there is no need to traverse parent nodes when we have a TNode flag (which would also * make this lookup O(1)). */ function isInSkipHydrationBlock(tNode) { let currentTNode = tNode.parent; while (currentTNode) { if (hasNgSkipHydrationAttr(currentTNode)) { return true; } currentTNode = currentTNode.parent; } return false; } /** * Flags for renderer-specific style modifiers. * @publicApi */ var RendererStyleFlags2; exports('RendererStyleFlags2', RendererStyleFlags2); (function (RendererStyleFlags2) { // TODO(misko): This needs to be refactored into a separate file so that it can be imported from // `node_manipulation.ts` Currently doing the import cause resolution order to change and fails // the tests. The work around is to have hard coded value in `node_manipulation.ts` for now. /** * Marks a style as important. */ RendererStyleFlags2[RendererStyleFlags2["Important"] = 1] = "Important"; /** * Marks a style as using dash case naming (this-is-dash-case). */ RendererStyleFlags2[RendererStyleFlags2["DashCase"] = 2] = "DashCase"; })(RendererStyleFlags2 || (exports('RendererStyleFlags2', RendererStyleFlags2 = {}))); /** * Disallowed strings in the comment. * * see: https://html.spec.whatwg.org/multipage/syntax.html#comments */ const COMMENT_DISALLOWED = /^>|^->||--!>|<!-$/g; /** * Delimiter in the disallowed strings which needs to be wrapped with zero with character. */ const COMMENT_DELIMITER = /(<|>)/; const COMMENT_DELIMITER_ESCAPED = '\u200B$1\u200B'; /** * Escape the content of comment strings so that it can be safely inserted into a comment node. * * The issue is that HTML does not specify any way to escape comment end text inside the comment. * Consider: `" or * "--!>" at the end. -->`. Above the `"-->"` is meant to be text not an end to the comment. This * can be created programmatically through DOM APIs. (`` or `--!>`) the * text it will render normally but it will not cause the HTML parser to close/open the comment. * * @param value text to make safe for comment node by escaping the comment open/close character * sequence. */ function escapeCommentText(value) { return value.replace(COMMENT_DISALLOWED, (text) => text.replace(COMMENT_DELIMITER, COMMENT_DELIMITER_ESCAPED)); } // Keeps track of the currently-active LViews. const TRACKED_LVIEWS = new Map(); // Used for generating unique IDs for LViews. let uniqueIdCounter = 0; /** Gets a unique ID that can be assigned to an LView. */ function getUniqueLViewId() { return uniqueIdCounter++; } /** Starts tracking an LView. */ function registerLView(lView) { ngDevMode && assertNumber(lView[ID], 'LView must have an ID in order to be registered'); TRACKED_LVIEWS.set(lView[ID], lView); } /** Gets an LView by its unique ID. */ function getLViewById(id) { ngDevMode && assertNumber(id, 'ID used for LView lookup must be a number'); return TRACKED_LVIEWS.get(id) || null; } /** Stops tracking an LView. */ function unregisterLView(lView) { ngDevMode && assertNumber(lView[ID], 'Cannot stop tracking an LView that does not have an ID'); TRACKED_LVIEWS.delete(lView[ID]); } /** * The internal view context which is specific to a given DOM element, directive or * component instance. Each value in here (besides the LView and element node details) * can be present, null or undefined. If undefined then it implies the value has not been * looked up yet, otherwise, if null, then a lookup was executed and nothing was found. * * Each value will get filled when the respective value is examined within the getContext * function. The component, element and each directive instance will share the same instance * of the context. */ class LContext { /** Component's parent view data. */ get lView() { return getLViewById(this.lViewId); } constructor( /** * ID of the component's parent view data. */ lViewId, /** * The index instance of the node. */ nodeIndex, /** * The instance of the DOM node that is attached to the lNode. */ native) { this.lViewId = lViewId; this.nodeIndex = nodeIndex; this.native = native; } } exports('ɵLContext', LContext); /** * Returns the matching `LContext` data for a given DOM node, directive or component instance. * * This function will examine the provided DOM element, component, or directive instance\'s * monkey-patched property to derive the `LContext` data. Once called then the monkey-patched * value will be that of the newly created `LContext`. * * If the monkey-patched value is the `LView` instance then the context value for that * target will be created and the monkey-patch reference will be updated. Therefore when this * function is called it may mutate the provided element\'s, component\'s or any of the associated * directive\'s monkey-patch values. * * If the monkey-patch value is not detected then the code will walk up the DOM until an element * is found which contains a monkey-patch reference. When that occurs then the provided element * will be updated with a new context (which is then returned). If the monkey-patch value is not * detected for a component/directive instance then it will throw an error (all components and * directives should be automatically monkey-patched by ivy). * * @param target Component, Directive or DOM Node. */ function getLContext(target) { let mpValue = readPatchedData(target); if (mpValue) { // only when it's an array is it considered an LView instance // ... otherwise it's an already constructed LContext instance if (isLView(mpValue)) { const lView = mpValue; let nodeIndex; let component = undefined; let directives = undefined; if (isComponentInstance(target)) { nodeIndex = findViaComponent(lView, target); if (nodeIndex == -1) { throw new Error('The provided component was not found in the application'); } component = target; } else if (isDirectiveInstance(target)) { nodeIndex = findViaDirective(lView, target); if (nodeIndex == -1) { throw new Error('The provided directive was not found in the application'); } directives = getDirectivesAtNodeIndex(nodeIndex, lView); } else { nodeIndex = findViaNativeElement(lView, target); if (nodeIndex == -1) { return null; } } // the goal is not to fill the entire context full of data because the lookups // are expensive. Instead, only the target data (the element, component, container, ICU // expression or directive details) are filled into the context. If called multiple times // with different target values then the missing target data will be filled in. const native = unwrapRNode(lView[nodeIndex]); const existingCtx = readPatchedData(native); const context = (existingCtx && !Array.isArray(existingCtx)) ? existingCtx : createLContext(lView, nodeIndex, native); // only when the component has been discovered then update the monkey-patch if (component && context.component === undefined) { context.component = component; attachPatchData(context.component, context); } // only when the directives have been discovered then update the monkey-patch if (directives && context.directives === undefined) { context.directives = directives; for (let i = 0; i < directives.length; i++) { attachPatchData(directives[i], context); } } attachPatchData(context.native, context); mpValue = context; } } else { const rElement = target; ngDevMode && assertDomNode(rElement); // if the context is not found then we need to traverse upwards up the DOM // to find the nearest element that has already been monkey patched with data let parent = rElement; while (parent = parent.parentNode) { const parentContext = readPatchedData(parent); if (parentContext) { const lView = Array.isArray(parentContext) ? parentContext : parentContext.lView; // the edge of the app was also reached here through another means // (maybe because the DOM was changed manually). if (!lView) { return null; } const index = findViaNativeElement(lView, rElement); if (index >= 0) { const native = unwrapRNode(lView[index]); const context = createLContext(lView, index, native); attachPatchData(native, context); mpValue = context; break; } } } } return mpValue || null; } /** * Creates an empty instance of a `LContext` context */ function createLContext(lView, nodeIndex, native) { return new LContext(lView[ID], nodeIndex, native); } /** * Takes a component instance and returns the view for that component. * * @param componentInstance * @returns The component's view */ function getComponentViewByInstance(componentInstance) { let patchedData = readPatchedData(componentInstance); let lView; if (isLView(patchedData)) { const contextLView = patchedData; const nodeIndex = findViaComponent(contextLView, componentInstance); lView = getComponentLViewByIndex(nodeIndex, contextLView); const context = createLContext(contextLView, nodeIndex, lView[HOST]); context.component = componentInstance; attachPatchData(componentInstance, context); attachPatchData(context.native, context); } else { const context = patchedData; const contextLView = context.lView; ngDevMode && assertLView(contextLView); lView = getComponentLViewByIndex(context.nodeIndex, contextLView); } return lView; } /** * This property will be monkey-patched on elements, components and directives. */ const MONKEY_PATCH_KEY_NAME = '__ngContext__'; /** * Assigns the given data to the given target (which could be a component, * directive or DOM node instance) using monkey-patching. */ function attachPatchData(target, data) { ngDevMode && assertDefined(target, 'Target expected'); // Only attach the ID of the view in order to avoid memory leaks (see #41047). We only do this // for `LView`, because we have control over when an `LView` is created and destroyed, whereas // we can't know when to remove an `LContext`. if (isLView(data)) { target[MONKEY_PATCH_KEY_NAME] = data[ID]; registerLView(data); } else { target[MONKEY_PATCH_KEY_NAME] = data; } } /** * Returns the monkey-patch value data present on the target (which could be * a component, directive or a DOM node). */ function readPatchedData(target) { ngDevMode && assertDefined(target, 'Target expected'); const data = target[MONKEY_PATCH_KEY_NAME]; return (typeof data === 'number') ? getLViewById(data) : data || null; } function readPatchedLView(target) { const value = readPatchedData(target); if (value) { return (isLView(value) ? value : value.lView); } return null; } function isComponentInstance(instance) { return instance && instance.constructor && instance.constructor.ɵcmp; } function isDirectiveInstance(instance) { return instance && instance.constructor && instance.constructor.ɵdir; } /** * Locates the element within the given LView and returns the matching index */ function findViaNativeElement(lView, target) { const tView = lView[TVIEW]; for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) { if (unwrapRNode(lView[i]) === target) { return i; } } return -1; } /** * Locates the next tNode (child, sibling or parent). */ function traverseNextElement(tNode) { if (tNode.child) { return tNode.child; } else if (tNode.next) { return tNode.next; } else { // Let's take the following template:
text
// After checking the text node, we need to find the next parent that has a "next" TNode, // in this case the parent `div`, so that we can find the component. while (tNode.parent && !tNode.parent.next) { tNode = tNode.parent; } return tNode.parent && tNode.parent.next; } } /** * Locates the component within the given LView and returns the matching index */ function findViaComponent(lView, componentInstance) { const componentIndices = lView[TVIEW].components; if (componentIndices) { for (let i = 0; i < componentIndices.length; i++) { const elementComponentIndex = componentIndices[i]; const componentView = getComponentLViewByIndex(elementComponentIndex, lView); if (componentView[CONTEXT] === componentInstance) { return elementComponentIndex; } } } else { const rootComponentView = getComponentLViewByIndex(HEADER_OFFSET, lView); const rootComponent = rootComponentView[CONTEXT]; if (rootComponent === componentInstance) { // we are dealing with the root element here therefore we know that the // element is the very first element after the HEADER data in the lView return HEADER_OFFSET; } } return -1; } /** * Locates the directive within the given LView and returns the matching index */ function findViaDirective(lView, directiveInstance) { // if a directive is monkey patched then it will (by default) // have a reference to the LView of the current view. The // element bound to the directive being search lives somewhere // in the view data. We loop through the nodes and check their // list of directives for the instance. let tNode = lView[TVIEW].firstChild; while (tNode) { const directiveIndexStart = tNode.directiveStart; const directiveIndexEnd = tNode.directiveEnd; for (let i = directiveIndexStart; i < directiveIndexEnd; i++) { if (lView[i] === directiveInstance) { return tNode.index; } } tNode = traverseNextElement(tNode); } return -1; } /** * Returns a list of directives applied to a node at a specific index. The list includes * directives matched by selector and any host directives, but it excludes components. * Use `getComponentAtNodeIndex` to find the component applied to a node. * * @param nodeIndex The node index * @param lView The target view data */ function getDirectivesAtNodeIndex(nodeIndex, lView) { const tNode = lView[TVIEW].data[nodeIndex]; if (tNode.directiveStart === 0) return EMPTY_ARRAY; const results = []; for (let i = tNode.directiveStart; i < tNode.directiveEnd; i++) { const directiveInstance = lView[i]; if (!isComponentInstance(directiveInstance)) { results.push(directiveInstance); } } return results; } function getComponentAtNodeIndex(nodeIndex, lView) { const tNode = lView[TVIEW].data[nodeIndex]; const { directiveStart, componentOffset } = tNode; return componentOffset > -1 ? lView[directiveStart + componentOffset] : null; } /** * Returns a map of local references (local reference name => element or directive instance) that * exist on a given element. */ function discoverLocalRefs(lView, nodeIndex) { const tNode = lView[TVIEW].data[nodeIndex]; if (tNode && tNode.localNames) { const result = {}; let localIndex = tNode.index + 1; for (let i = 0; i < tNode.localNames.length; i += 2) { result[tNode.localNames[i]] = lView[localIndex]; localIndex++; } return result; } return null; } let _icuContainerIterate; /** * Iterator which provides ability to visit all of the `TIcuContainerNode` root `RNode`s. */ function icuContainerIterate(tIcuContainerNode, lView) { return _icuContainerIterate(tIcuContainerNode, lView); } /** * Ensures that `IcuContainerVisitor`'s implementation is present. * * This function is invoked when i18n instruction comes across an ICU. The purpose is to allow the * bundler to tree shake ICU logic and only load it if ICU instruction is executed. */ function ensureIcuContainerVisitorLoaded(loader) { if (_icuContainerIterate === undefined) { // Do not inline this function. We want to keep `ensureIcuContainerVisitorLoaded` light, so it // can be inlined into call-site. _icuContainerIterate = loader(); } } /** * Gets the parent LView of the passed LView, if the PARENT is an LContainer, will get the parent of * that LContainer, which is an LView * @param lView the lView whose parent to get */ function getLViewParent(lView) { ngDevMode && assertLView(lView); const parent = lView[PARENT]; return isLContainer(parent) ? parent[PARENT] : parent; } /** * Retrieve the root view from any component or `LView` by walking the parent `LView` until * reaching the root `LView`. * * @param componentOrLView any component or `LView` */ function getRootView(componentOrLView) { ngDevMode && assertDefined(componentOrLView, 'component'); let lView = isLView(componentOrLView) ? componentOrLView : readPatchedLView(componentOrLView); while (lView && !(lView[FLAGS] & 512 /* LViewFlags.IsRoot */)) { lView = getLViewParent(lView); } ngDevMode && assertLView(lView); return lView; } /** * Returns the context information associated with the application where the target is situated. It * does this by walking the parent views until it gets to the root view, then getting the context * off of that. * * @param viewOrComponent the `LView` or component to get the root context for. */ function getRootContext(viewOrComponent) { const rootView = getRootView(viewOrComponent); ngDevMode && assertDefined(rootView[CONTEXT], 'Root view has no context. Perhaps it is disconnected?'); return rootView[CONTEXT]; } /** * Gets the first `LContainer` in the LView or `null` if none exists. */ function getFirstLContainer(lView) { return getNearestLContainer(lView[CHILD_HEAD]); } /** * Gets the next `LContainer` that is a sibling of the given container. */ function getNextLContainer(container) { return getNearestLContainer(container[NEXT]); } function getNearestLContainer(viewOrContainer) { while (viewOrContainer !== null && !isLContainer(viewOrContainer)) { viewOrContainer = viewOrContainer[NEXT]; } return viewOrContainer; } /** * NOTE: for performance reasons, the possible actions are inlined within the function instead of * being passed as an argument. */ function applyToElementOrContainer(action, renderer, parent, lNodeToHandle, beforeNode) { // If this slot was allocated for a text node dynamically created by i18n, the text node itself // won't be created until i18nApply() in the update block, so this node should be skipped. // For more info, see "ICU expressions should work inside an ngTemplateOutlet inside an ngFor" // in `i18n_spec.ts`. if (lNodeToHandle != null) { let lContainer; let isComponent = false; // We are expecting an RNode, but in the case of a component or LContainer the `RNode` is // wrapped in an array which needs to be unwrapped. We need to know if it is a component and if // it has LContainer so that we can process all of those cases appropriately. if (isLContainer(lNodeToHandle)) { lContainer = lNodeToHandle; } else if (isLView(lNodeToHandle)) { isComponent = true; ngDevMode && assertDefined(lNodeToHandle[HOST], 'HOST must be defined for a component LView'); lNodeToHandle = lNodeToHandle[HOST]; } const rNode = unwrapRNode(lNodeToHandle); if (action === 0 /* WalkTNodeTreeAction.Create */ && parent !== null) { if (beforeNode == null) { nativeAppendChild(renderer, parent, rNode); } else { nativeInsertBefore(renderer, parent, rNode, beforeNode || null, true); } } else if (action === 1 /* WalkTNodeTreeAction.Insert */ && parent !== null) { nativeInsertBefore(renderer, parent, rNode, beforeNode || null, true); } else if (action === 2 /* WalkTNodeTreeAction.Detach */) { nativeRemoveNode(renderer, rNode, isComponent); } else if (action === 3 /* WalkTNodeTreeAction.Destroy */) { ngDevMode && ngDevMode.rendererDestroyNode++; renderer.destroyNode(rNode); } if (lContainer != null) { applyContainer(renderer, action, lContainer, parent, beforeNode); } } } function createTextNode(renderer, value) { ngDevMode && ngDevMode.rendererCreateTextNode++; ngDevMode && ngDevMode.rendererSetText++; return renderer.createText(value); } function updateTextNode(renderer, rNode, value) { ngDevMode && ngDevMode.rendererSetText++; renderer.setValue(rNode, value); } function createCommentNode(renderer, value) { ngDevMode && ngDevMode.rendererCreateComment++; return renderer.createComment(escapeCommentText(value)); } /** * Creates a native element from a tag name, using a renderer. * @param renderer A renderer to use * @param name the tag name * @param namespace Optional namespace for element. * @returns the element created */ function createElementNode(renderer, name, namespace) { ngDevMode && ngDevMode.rendererCreateElement++; return renderer.createElement(name, namespace); } /** * Removes all DOM elements associated with a view. * * Because some root nodes of the view may be containers, we sometimes need * to propagate deeply into the nested containers to remove all elements in the * views beneath it. * * @param tView The `TView' of the `LView` from which elements should be added or removed * @param lView The view from which elements should be added or removed */ function removeViewFromContainer(tView, lView) { const renderer = lView[RENDERER]; applyView(tView, lView, renderer, 2 /* WalkTNodeTreeAction.Detach */, null, null); lView[HOST] = null; lView[T_HOST] = null; } /** * Adds all DOM elements associated with a view. * * Because some root nodes of the view may be containers, we sometimes need * to propagate deeply into the nested containers to add all elements in the * views beneath it. * * @param tView The `TView' of the `LView` from which elements should be added or removed * @param parentTNode The `TNode` where the `LView` should be attached to. * @param renderer Current renderer to use for DOM manipulations. * @param lView The view from which elements should be added or removed * @param parentNativeNode The parent `RElement` where it should be inserted into. * @param beforeNode The node before which elements should be added, if insert mode */ function addViewToContainer(tView, parentTNode, renderer, lView, parentNativeNode, beforeNode) { lView[HOST] = parentNativeNode; lView[T_HOST] = parentTNode; applyView(tView, lView, renderer, 1 /* WalkTNodeTreeAction.Insert */, parentNativeNode, beforeNode); } /** * Detach a `LView` from the DOM by detaching its nodes. * * @param tView The `TView' of the `LView` to be detached * @param lView the `LView` to be detached. */ function renderDetachView(tView, lView) { applyView(tView, lView, lView[RENDERER], 2 /* WalkTNodeTreeAction.Detach */, null, null); } /** * Traverses down and up the tree of views and containers to remove listeners and * call onDestroy callbacks. * * Notes: * - Because it's used for onDestroy calls, it needs to be bottom-up. * - Must process containers instead of their views to avoid splicing * when views are destroyed and re-added. * - Using a while loop because it's faster than recursion * - Destroy only called on movement to sibling or movement to parent (laterally or up) * * @param rootView The view to destroy */ function destroyViewTree(rootView) { // If the view has no children, we can clean it up and return early. let lViewOrLContainer = rootView[CHILD_HEAD]; if (!lViewOrLContainer) { return cleanUpView(rootView[TVIEW], rootView); } while (lViewOrLContainer) { let next = null; if (isLView(lViewOrLContainer)) { // If LView, traverse down to child. next = lViewOrLContainer[CHILD_HEAD]; } else { ngDevMode && assertLContainer(lViewOrLContainer); // If container, traverse down to its first LView. const firstView = lViewOrLContainer[CONTAINER_HEADER_OFFSET]; if (firstView) next = firstView; } if (!next) { // Only clean up view when moving to the side or up, as destroy hooks // should be called in order from the bottom up. while (lViewOrLContainer && !lViewOrLContainer[NEXT] && lViewOrLContainer !== rootView) { if (isLView(lViewOrLContainer)) { cleanUpView(lViewOrLContainer[TVIEW], lViewOrLContainer); } lViewOrLContainer = lViewOrLContainer[PARENT]; } if (lViewOrLContainer === null) lViewOrLContainer = rootView; if (isLView(lViewOrLContainer)) { cleanUpView(lViewOrLContainer[TVIEW], lViewOrLContainer); } next = lViewOrLContainer && lViewOrLContainer[NEXT]; } lViewOrLContainer = next; } } /** * Inserts a view into a container. * * This adds the view to the container's array of active views in the correct * position. It also adds the view's elements to the DOM if the container isn't a * root node of another view (in that case, the view's elements will be added when * the container's parent view is added later). * * @param tView The `TView' of the `LView` to insert * @param lView The view to insert * @param lContainer The container into which the view should be inserted * @param index Which index in the container to insert the child view into */ function insertView(tView, lView, lContainer, index) { ngDevMode && assertLView(lView); ngDevMode && assertLContainer(lContainer); const indexInContainer = CONTAINER_HEADER_OFFSET + index; const containerLength = lContainer.length; if (index > 0) { // This is a new view, we need to add it to the children. lContainer[indexInContainer - 1][NEXT] = lView; } if (index < containerLength - CONTAINER_HEADER_OFFSET) { lView[NEXT] = lContainer[indexInContainer]; addToArray(lContainer, CONTAINER_HEADER_OFFSET + index, lView); } else { lContainer.push(lView); lView[NEXT] = null; } lView[PARENT] = lContainer; // track views where declaration and insertion points are different const declarationLContainer = lView[DECLARATION_LCONTAINER]; if (declarationLContainer !== null && lContainer !== declarationLContainer) { trackMovedView(declarationLContainer, lView); } // notify query that a new view has been added const lQueries = lView[QUERIES]; if (lQueries !== null) { lQueries.insertView(tView); } // Sets the attached flag lView[FLAGS] |= 128 /* LViewFlags.Attached */; } /** * Track views created from the declaration container (TemplateRef) and inserted into a * different LContainer. */ function trackMovedView(declarationContainer, lView) { ngDevMode && assertDefined(lView, 'LView required'); ngDevMode && assertLContainer(declarationContainer); const movedViews = declarationContainer[MOVED_VIEWS]; const insertedLContainer = lView[PARENT]; ngDevMode && assertLContainer(insertedLContainer); const insertedComponentLView = insertedLContainer[PARENT][DECLARATION_COMPONENT_VIEW]; ngDevMode && assertDefined(insertedComponentLView, 'Missing insertedComponentLView'); const declaredComponentLView = lView[DECLARATION_COMPONENT_VIEW]; ngDevMode && assertDefined(declaredComponentLView, 'Missing declaredComponentLView'); if (declaredComponentLView !== insertedComponentLView) { // At this point the declaration-component is not same as insertion-component; this means that // this is a transplanted view. Mark the declared lView as having transplanted views so that // those views can participate in CD. declarationContainer[HAS_TRANSPLANTED_VIEWS] = true; } if (movedViews === null) { declarationContainer[MOVED_VIEWS] = [lView]; } else { movedViews.push(lView); } } function detachMovedView(declarationContainer, lView) { ngDevMode && assertLContainer(declarationContainer); ngDevMode && assertDefined(declarationContainer[MOVED_VIEWS], 'A projected view should belong to a non-empty projected views collection'); const movedViews = declarationContainer[MOVED_VIEWS]; const declarationViewIndex = movedViews.indexOf(lView); const insertionLContainer = lView[PARENT]; ngDevMode && assertLContainer(insertionLContainer); // If the view was marked for refresh but then detached before it was checked (where the flag // would be cleared and the counter decremented), we need to update the status here. clearViewRefreshFlag(lView); movedViews.splice(declarationViewIndex, 1); } /** * Detaches a view from a container. * * This method removes the view from the container's array of active views. It also * removes the view's elements from the DOM. * * @param lContainer The container from which to detach a view * @param removeIndex The index of the view to detach * @returns Detached LView instance. */ function detachView(lContainer, removeIndex) { if (lContainer.length <= CONTAINER_HEADER_OFFSET) return; const indexInContainer = CONTAINER_HEADER_OFFSET + removeIndex; const viewToDetach = lContainer[indexInContainer]; if (viewToDetach) { const declarationLContainer = viewToDetach[DECLARATION_LCONTAINER]; if (declarationLContainer !== null && declarationLContainer !== lContainer) { detachMovedView(declarationLContainer, viewToDetach); } if (removeIndex > 0) { lContainer[indexInContainer - 1][NEXT] = viewToDetach[NEXT]; } const removedLView = removeFromArray(lContainer, CONTAINER_HEADER_OFFSET + removeIndex); removeViewFromContainer(viewToDetach[TVIEW], viewToDetach); // notify query that a view has been removed const lQueries = removedLView[QUERIES]; if (lQueries !== null) { lQueries.detachView(removedLView[TVIEW]); } viewToDetach[PARENT] = null; viewToDetach[NEXT] = null; // Unsets the attached flag viewToDetach[FLAGS] &= ~128 /* LViewFlags.Attached */; } return viewToDetach; } /** * A standalone function which destroys an LView, * conducting clean up (e.g. removing listeners, calling onDestroys). * * @param tView The `TView' of the `LView` to be destroyed * @param lView The view to be destroyed. */ function destroyLView(tView, lView) { if (!(lView[FLAGS] & 256 /* LViewFlags.Destroyed */)) { const renderer = lView[RENDERER]; lView[REACTIVE_TEMPLATE_CONSUMER]?.destroy(); lView[REACTIVE_HOST_BINDING_CONSUMER]?.destroy(); if (renderer.destroyNode) { applyView(tView, lView, renderer, 3 /* WalkTNodeTreeAction.Destroy */, null, null); } destroyViewTree(lView); } } /** * Calls onDestroys hooks for all directives and pipes in a given view and then removes all * listeners. Listeners are removed as the last step so events delivered in the onDestroys hooks * can be propagated to @Output listeners. * * @param tView `TView` for the `LView` to clean up. * @param lView The LView to clean up */ function cleanUpView(tView, lView) { if (!(lView[FLAGS] & 256 /* LViewFlags.Destroyed */)) { // Usually the Attached flag is removed when the view is detached from its parent, however // if it's a root view, the flag won't be unset hence why we're also removing on destroy. lView[FLAGS] &= ~128 /* LViewFlags.Attached */; // Mark the LView as destroyed *before* executing the onDestroy hooks. An onDestroy hook // runs arbitrary user code, which could include its own `viewRef.destroy()` (or similar). If // We don't flag the view as destroyed before the hooks, this could lead to an infinite loop. // This also aligns with the ViewEngine behavior. It also means that the onDestroy hook is // really more of an "afterDestroy" hook if you think about it. lView[FLAGS] |= 256 /* LViewFlags.Destroyed */; executeOnDestroys(tView, lView); processCleanups(tView, lView); // For component views only, the local renderer is destroyed at clean up time. if (lView[TVIEW].type === 1 /* TViewType.Component */) { ngDevMode && ngDevMode.rendererDestroy++; lView[RENDERER].destroy(); } const declarationContainer = lView[DECLARATION_LCONTAINER]; // we are dealing with an embedded view that is still inserted into a container if (declarationContainer !== null && isLContainer(lView[PARENT])) { // and this is a projected view if (declarationContainer !== lView[PARENT]) { detachMovedView(declarationContainer, lView); } // For embedded views still attached to a container: remove query result from this view. const lQueries = lView[QUERIES]; if (lQueries !== null) { lQueries.detachView(tView); } } // Unregister the view once everything else has been cleaned up. unregisterLView(lView); } } /** Removes listeners and unsubscribes from output subscriptions */ function processCleanups(tView, lView) { const tCleanup = tView.cleanup; const lCleanup = lView[CLEANUP]; if (tCleanup !== null) { for (let i = 0; i < tCleanup.length - 1; i += 2) { if (typeof tCleanup[i] === 'string') { // This is a native DOM listener. It will occupy 4 entries in the TCleanup array (hence i += // 2 at the end of this block). const targetIdx = tCleanup[i + 3]; ngDevMode && assertNumber(targetIdx, 'cleanup target must be a number'); if (targetIdx >= 0) { // unregister lCleanup[targetIdx](); } else { // Subscription lCleanup[-targetIdx].unsubscribe(); } i += 2; } else { // This is a cleanup function that is grouped with the index of its context const context = lCleanup[tCleanup[i + 1]]; tCleanup[i].call(context); } } } if (lCleanup !== null) { lView[CLEANUP] = null; } const destroyHooks = lView[ON_DESTROY_HOOKS]; if (destroyHooks !== null) { // Reset the ON_DESTROY_HOOKS array before iterating over it to prevent hooks that unregister // themselves from mutating the array during iteration. lView[ON_DESTROY_HOOKS] = null; for (let i = 0; i < destroyHooks.length; i++) { const destroyHooksFn = destroyHooks[i]; ngDevMode && assertFunction(destroyHooksFn, 'Expecting destroy hook to be a function.'); destroyHooksFn(); } } } /** Calls onDestroy hooks for this view */ function executeOnDestroys(tView, lView) { let destroyHooks; if (tView != null && (destroyHooks = tView.destroyHooks) != null) { for (let i = 0; i < destroyHooks.length; i += 2) { const context = lView[destroyHooks[i]]; // Only call the destroy hook if the context has been requested. if (!(context instanceof NodeInjectorFactory)) { const toCall = destroyHooks[i + 1]; if (Array.isArray(toCall)) { for (let j = 0; j < toCall.length; j += 2) { const callContext = context[toCall[j]]; const hook = toCall[j + 1]; profiler(4 /* ProfilerEvent.LifecycleHookStart */, callContext, hook); try { hook.call(callContext); } finally { profiler(5 /* ProfilerEvent.LifecycleHookEnd */, callContext, hook); } } } else { profiler(4 /* ProfilerEvent.LifecycleHookStart */, context, toCall); try { toCall.call(context); } finally { profiler(5 /* ProfilerEvent.LifecycleHookEnd */, context, toCall); } } } } } } /** * Returns a native element if a node can be inserted into the given parent. * * There are two reasons why we may not be able to insert a element immediately. * - Projection: When creating a child content element of a component, we have to skip the * insertion because the content of a component will be projected. * `delayed due to projection` * - Parent container is disconnected: This can happen when we are inserting a view into * parent container, which itself is disconnected. For example the parent container is part * of a View which has not be inserted or is made for projection but has not been inserted * into destination. * * @param tView: Current `TView`. * @param tNode: `TNode` for which we wish to retrieve render parent. * @param lView: Current `LView`. */ function getParentRElement(tView, tNode, lView) { return getClosestRElement(tView, tNode.parent, lView); } /** * Get closest `RElement` or `null` if it can't be found. * * If `TNode` is `TNodeType.Element` => return `RElement` at `LView[tNode.index]` location. * If `TNode` is `TNodeType.ElementContainer|IcuContain` => return the parent (recursively). * If `TNode` is `null` then return host `RElement`: * - return `null` if projection * - return `null` if parent container is disconnected (we have no parent.) * * @param tView: Current `TView`. * @param tNode: `TNode` for which we wish to retrieve `RElement` (or `null` if host element is * needed). * @param lView: Current `LView`. * @returns `null` if the `RElement` can't be determined at this time (no parent / projection) */ function getClosestRElement(tView, tNode, lView) { let parentTNode = tNode; // Skip over element and ICU containers as those are represented by a comment node and // can't be used as a render parent. while (parentTNode !== null && (parentTNode.type & (8 /* TNodeType.ElementContainer */ | 32 /* TNodeType.Icu */))) { tNode = parentTNode; parentTNode = tNode.parent; } // If the parent tNode is null, then we are inserting across views: either into an embedded view // or a component view. if (parentTNode === null) { // We are inserting a root element of the component view into the component host element and // it should always be eager. return lView[HOST]; } else { ngDevMode && assertTNodeType(parentTNode, 3 /* TNodeType.AnyRNode */ | 4 /* TNodeType.Container */); const { componentOffset } = parentTNode; if (componentOffset > -1) { ngDevMode && assertTNodeForLView(parentTNode, lView); const { encapsulation } = tView.data[parentTNode.directiveStart + componentOffset]; // We've got a parent which is an element in the current view. We just need to verify if the // parent element is not a component. Component's content nodes are not inserted immediately // because they will be projected, and so doing insert at this point would be wasteful. // Since the projection would then move it to its final destination. Note that we can't // make this assumption when using the Shadow DOM, because the native projection placeholders // ( or ) have to be in place as elements are being inserted. if (encapsulation === ViewEncapsulation$1.None || encapsulation === ViewEncapsulation$1.Emulated) { return null; } } return getNativeByTNode(parentTNode, lView); } } /** * Inserts a native node before another native node for a given parent. * This is a utility function that can be used when native nodes were determined. */ function nativeInsertBefore(renderer, parent, child, beforeNode, isMove) { ngDevMode && ngDevMode.rendererInsertBefore++; renderer.insertBefore(parent, child, beforeNode, isMove); } function nativeAppendChild(renderer, parent, child) { ngDevMode && ngDevMode.rendererAppendChild++; ngDevMode && assertDefined(parent, 'parent node must be defined'); renderer.appendChild(parent, child); } function nativeAppendOrInsertBefore(renderer, parent, child, beforeNode, isMove) { if (beforeNode !== null) { nativeInsertBefore(renderer, parent, child, beforeNode, isMove); } else { nativeAppendChild(renderer, parent, child); } } /** Removes a node from the DOM given its native parent. */ function nativeRemoveChild(renderer, parent, child, isHostElement) { renderer.removeChild(parent, child, isHostElement); } /** * Returns a native parent of a given native node. */ function nativeParentNode(renderer, node) { return renderer.parentNode(node); } /** * Returns a native sibling of a given native node. */ function nativeNextSibling(renderer, node) { return renderer.nextSibling(node); } /** * Find a node in front of which `currentTNode` should be inserted. * * This method determines the `RNode` in front of which we should insert the `currentRNode`. This * takes `TNode.insertBeforeIndex` into account if i18n code has been invoked. * * @param parentTNode parent `TNode` * @param currentTNode current `TNode` (The node which we would like to insert into the DOM) * @param lView current `LView` */ function getInsertInFrontOfRNode(parentTNode, currentTNode, lView) { return _getInsertInFrontOfRNodeWithI18n(parentTNode, currentTNode, lView); } /** * Find a node in front of which `currentTNode` should be inserted. (Does not take i18n into * account) * * This method determines the `RNode` in front of which we should insert the `currentRNode`. This * does not take `TNode.insertBeforeIndex` into account. * * @param parentTNode parent `TNode` * @param currentTNode current `TNode` (The node which we would like to insert into the DOM) * @param lView current `LView` */ function getInsertInFrontOfRNodeWithNoI18n(parentTNode, currentTNode, lView) { if (parentTNode.type & (8 /* TNodeType.ElementContainer */ | 32 /* TNodeType.Icu */)) { return getNativeByTNode(parentTNode, lView); } return null; } /** * Tree shakable boundary for `getInsertInFrontOfRNodeWithI18n` function. * * This function will only be set if i18n code runs. */ let _getInsertInFrontOfRNodeWithI18n = getInsertInFrontOfRNodeWithNoI18n; /** * Tree shakable boundary for `processI18nInsertBefore` function. * * This function will only be set if i18n code runs. */ let _processI18nInsertBefore; function setI18nHandling(getInsertInFrontOfRNodeWithI18n, processI18nInsertBefore) { _getInsertInFrontOfRNodeWithI18n = getInsertInFrontOfRNodeWithI18n; _processI18nInsertBefore = processI18nInsertBefore; } /** * Appends the `child` native node (or a collection of nodes) to the `parent`. * * @param tView The `TView' to be appended * @param lView The current LView * @param childRNode The native child (or children) that should be appended * @param childTNode The TNode of the child element */ function appendChild(tView, lView, childRNode, childTNode) { const parentRNode = getParentRElement(tView, childTNode, lView); const renderer = lView[RENDERER]; const parentTNode = childTNode.parent || lView[T_HOST]; const anchorNode = getInsertInFrontOfRNode(parentTNode, childTNode, lView); if (parentRNode != null) { if (Array.isArray(childRNode)) { for (let i = 0; i < childRNode.length; i++) { nativeAppendOrInsertBefore(renderer, parentRNode, childRNode[i], anchorNode, false); } } else { nativeAppendOrInsertBefore(renderer, parentRNode, childRNode, anchorNode, false); } } _processI18nInsertBefore !== undefined && _processI18nInsertBefore(renderer, childTNode, lView, childRNode, parentRNode); } /** * Returns the first native node for a given LView, starting from the provided TNode. * * Native nodes are returned in the order in which those appear in the native tree (DOM). */ function getFirstNativeNode(lView, tNode) { if (tNode !== null) { ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */ | 32 /* TNodeType.Icu */ | 16 /* TNodeType.Projection */); const tNodeType = tNode.type; if (tNodeType & 3 /* TNodeType.AnyRNode */) { return getNativeByTNode(tNode, lView); } else if (tNodeType & 4 /* TNodeType.Container */) { return getBeforeNodeForView(-1, lView[tNode.index]); } else if (tNodeType & 8 /* TNodeType.ElementContainer */) { const elIcuContainerChild = tNode.child; if (elIcuContainerChild !== null) { return getFirstNativeNode(lView, elIcuContainerChild); } else { const rNodeOrLContainer = lView[tNode.index]; if (isLContainer(rNodeOrLContainer)) { return getBeforeNodeForView(-1, rNodeOrLContainer); } else { return unwrapRNode(rNodeOrLContainer); } } } else if (tNodeType & 32 /* TNodeType.Icu */) { let nextRNode = icuContainerIterate(tNode, lView); let rNode = nextRNode(); // If the ICU container has no nodes, than we use the ICU anchor as the node. return rNode || unwrapRNode(lView[tNode.index]); } else { const projectionNodes = getProjectionNodes(lView, tNode); if (projectionNodes !== null) { if (Array.isArray(projectionNodes)) { return projectionNodes[0]; } const parentView = getLViewParent(lView[DECLARATION_COMPONENT_VIEW]); ngDevMode && assertParentView(parentView); return getFirstNativeNode(parentView, projectionNodes); } else { return getFirstNativeNode(lView, tNode.next); } } } return null; } function getProjectionNodes(lView, tNode) { if (tNode !== null) { const componentView = lView[DECLARATION_COMPONENT_VIEW]; const componentHost = componentView[T_HOST]; const slotIdx = tNode.projection; ngDevMode && assertProjectionSlots(lView); return componentHost.projection[slotIdx]; } return null; } function getBeforeNodeForView(viewIndexInContainer, lContainer) { const nextViewIndex = CONTAINER_HEADER_OFFSET + viewIndexInContainer + 1; if (nextViewIndex < lContainer.length) { const lView = lContainer[nextViewIndex]; const firstTNodeOfView = lView[TVIEW].firstChild; if (firstTNodeOfView !== null) { return getFirstNativeNode(lView, firstTNodeOfView); } } return lContainer[NATIVE]; } /** * Removes a native node itself using a given renderer. To remove the node we are looking up its * parent from the native tree as not all platforms / browsers support the equivalent of * node.remove(). * * @param renderer A renderer to be used * @param rNode The native node that should be removed * @param isHostElement A flag indicating if a node to be removed is a host of a component. */ function nativeRemoveNode(renderer, rNode, isHostElement) { ngDevMode && ngDevMode.rendererRemoveNode++; const nativeParent = nativeParentNode(renderer, rNode); if (nativeParent) { nativeRemoveChild(renderer, nativeParent, rNode, isHostElement); } } /** * Clears the contents of a given RElement. * * @param rElement the native RElement to be cleared */ function clearElementContents(rElement) { rElement.textContent = ''; } /** * Performs the operation of `action` on the node. Typically this involves inserting or removing * nodes on the LView or projection boundary. */ function applyNodes(renderer, action, tNode, lView, parentRElement, beforeNode, isProjection) { while (tNode != null) { ngDevMode && assertTNodeForLView(tNode, lView); ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */ | 16 /* TNodeType.Projection */ | 32 /* TNodeType.Icu */); const rawSlotValue = lView[tNode.index]; const tNodeType = tNode.type; if (isProjection) { if (action === 0 /* WalkTNodeTreeAction.Create */) { rawSlotValue && attachPatchData(unwrapRNode(rawSlotValue), lView); tNode.flags |= 2 /* TNodeFlags.isProjected */; } } if ((tNode.flags & 32 /* TNodeFlags.isDetached */) !== 32 /* TNodeFlags.isDetached */) { if (tNodeType & 8 /* TNodeType.ElementContainer */) { applyNodes(renderer, action, tNode.child, lView, parentRElement, beforeNode, false); applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); } else if (tNodeType & 32 /* TNodeType.Icu */) { const nextRNode = icuContainerIterate(tNode, lView); let rNode; while (rNode = nextRNode()) { applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode); } applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); } else if (tNodeType & 16 /* TNodeType.Projection */) { applyProjectionRecursive(renderer, action, lView, tNode, parentRElement, beforeNode); } else { ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 4 /* TNodeType.Container */); applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); } } tNode = isProjection ? tNode.projectionNext : tNode.next; } } function applyView(tView, lView, renderer, action, parentRElement, beforeNode) { applyNodes(renderer, action, tView.firstChild, lView, parentRElement, beforeNode, false); } /** * `applyProjection` performs operation on the projection. * * Inserting a projection requires us to locate the projected nodes from the parent component. The * complication is that those nodes themselves could be re-projected from their parent component. * * @param tView The `TView` of `LView` which needs to be inserted, detached, destroyed * @param lView The `LView` which needs to be inserted, detached, destroyed. * @param tProjectionNode node to project */ function applyProjection(tView, lView, tProjectionNode) { const renderer = lView[RENDERER]; const parentRNode = getParentRElement(tView, tProjectionNode, lView); const parentTNode = tProjectionNode.parent || lView[T_HOST]; let beforeNode = getInsertInFrontOfRNode(parentTNode, tProjectionNode, lView); applyProjectionRecursive(renderer, 0 /* WalkTNodeTreeAction.Create */, lView, tProjectionNode, parentRNode, beforeNode); } /** * `applyProjectionRecursive` performs operation on the projection specified by `action` (insert, * detach, destroy) * * Inserting a projection requires us to locate the projected nodes from the parent component. The * complication is that those nodes themselves could be re-projected from their parent component. * * @param renderer Render to use * @param action action to perform (insert, detach, destroy) * @param lView The LView which needs to be inserted, detached, destroyed. * @param tProjectionNode node to project * @param parentRElement parent DOM element for insertion/removal. * @param beforeNode Before which node the insertions should happen. */ function applyProjectionRecursive(renderer, action, lView, tProjectionNode, parentRElement, beforeNode) { const componentLView = lView[DECLARATION_COMPONENT_VIEW]; const componentNode = componentLView[T_HOST]; ngDevMode && assertEqual(typeof tProjectionNode.projection, 'number', 'expecting projection index'); const nodeToProjectOrRNodes = componentNode.projection[tProjectionNode.projection]; if (Array.isArray(nodeToProjectOrRNodes)) { // This should not exist, it is a bit of a hack. When we bootstrap a top level node and we // need to support passing projectable nodes, so we cheat and put them in the TNode // of the Host TView. (Yes we put instance info at the T Level). We can get away with it // because we know that that TView is not shared and therefore it will not be a problem. // This should be refactored and cleaned up. for (let i = 0; i < nodeToProjectOrRNodes.length; i++) { const rNode = nodeToProjectOrRNodes[i]; applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode); } } else { let nodeToProject = nodeToProjectOrRNodes; const projectedComponentLView = componentLView[PARENT]; // If a parent is located within a skip hydration block, // annotate an actual node that is being projected with the same flag too. if (hasInSkipHydrationBlockFlag(tProjectionNode)) { nodeToProject.flags |= 128 /* TNodeFlags.inSkipHydrationBlock */; } applyNodes(renderer, action, nodeToProject, projectedComponentLView, parentRElement, beforeNode, true); } } /** * `applyContainer` performs an operation on the container and its views as specified by * `action` (insert, detach, destroy) * * Inserting a Container is complicated by the fact that the container may have Views which * themselves have containers or projections. * * @param renderer Renderer to use * @param action action to perform (insert, detach, destroy) * @param lContainer The LContainer which needs to be inserted, detached, destroyed. * @param parentRElement parent DOM element for insertion/removal. * @param beforeNode Before which node the insertions should happen. */ function applyContainer(renderer, action, lContainer, parentRElement, beforeNode) { ngDevMode && assertLContainer(lContainer); const anchor = lContainer[NATIVE]; // LContainer has its own before node. const native = unwrapRNode(lContainer); // An LContainer can be created dynamically on any node by injecting ViewContainerRef. // Asking for a ViewContainerRef on an element will result in a creation of a separate anchor // node (comment in the DOM) that will be different from the LContainer's host node. In this // particular case we need to execute action on 2 nodes: // - container's host node (this is done in the executeActionOnElementOrContainer) // - container's host node (this is done here) if (anchor !== native) { // This is very strange to me (Misko). I would expect that the native is same as anchor. I // don't see a reason why they should be different, but they are. // // If they are we need to process the second anchor as well. applyToElementOrContainer(action, renderer, parentRElement, anchor, beforeNode); } for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) { const lView = lContainer[i]; applyView(lView[TVIEW], lView, renderer, action, parentRElement, anchor); } } /** * Writes class/style to element. * * @param renderer Renderer to use. * @param isClassBased `true` if it should be written to `class` (`false` to write to `style`) * @param rNode The Node to write to. * @param prop Property to write to. This would be the class/style name. * @param value Value to write. If `null`/`undefined`/`false` this is considered a remove (set/add * otherwise). */ function applyStyling(renderer, isClassBased, rNode, prop, value) { if (isClassBased) { // We actually want JS true/false here because any truthy value should add the class if (!value) { ngDevMode && ngDevMode.rendererRemoveClass++; renderer.removeClass(rNode, prop); } else { ngDevMode && ngDevMode.rendererAddClass++; renderer.addClass(rNode, prop); } } else { let flags = prop.indexOf('-') === -1 ? undefined : RendererStyleFlags2.DashCase; if (value == null /** || value === undefined */) { ngDevMode && ngDevMode.rendererRemoveStyle++; renderer.removeStyle(rNode, prop, flags); } else { // A value is important if it ends with `!important`. The style // parser strips any semicolons at the end of the value. const isImportant = typeof value === 'string' ? value.endsWith('!important') : false; if (isImportant) { // !important has to be stripped from the value for it to be valid. value = value.slice(0, -10); flags |= RendererStyleFlags2.Important; } ngDevMode && ngDevMode.rendererSetStyle++; renderer.setStyle(rNode, prop, value, flags); } } } /** * Write `cssText` to `RElement`. * * This function does direct write without any reconciliation. Used for writing initial values, so * that static styling values do not pull in the style parser. * * @param renderer Renderer to use * @param element The element which needs to be updated. * @param newValue The new class list to write. */ function writeDirectStyle(renderer, element, newValue) { ngDevMode && assertString(newValue, '\'newValue\' should be a string'); renderer.setAttribute(element, 'style', newValue); ngDevMode && ngDevMode.rendererSetStyle++; } /** * Write `className` to `RElement`. * * This function does direct write without any reconciliation. Used for writing initial values, so * that static styling values do not pull in the style parser. * * @param renderer Renderer to use * @param element The element which needs to be updated. * @param newValue The new class list to write. */ function writeDirectClass(renderer, element, newValue) { ngDevMode && assertString(newValue, '\'newValue\' should be a string'); if (newValue === '') { // There are tests in `google3` which expect `element.getAttribute('class')` to be `null`. renderer.removeAttribute(element, 'class'); } else { renderer.setAttribute(element, 'class', newValue); } ngDevMode && ngDevMode.rendererSetClassName++; } /** Sets up the static DOM attributes on an `RNode`. */ function setupStaticAttributes(renderer, element, tNode) { const { mergedAttrs, classes, styles } = tNode; if (mergedAttrs !== null) { setUpAttributes(renderer, element, mergedAttrs); } if (classes !== null) { writeDirectClass(renderer, element, classes); } if (styles !== null) { writeDirectStyle(renderer, element, styles); } } /** * @fileoverview * A module to facilitate use of a Trusted Types policy internally within * Angular. It lazily constructs the Trusted Types policy, providing helper * utilities for promoting strings to Trusted Types. When Trusted Types are not * available, strings are used as a fallback. * @security All use of this module is security-sensitive and should go through * security review. */ /** * The Trusted Types policy, or null if Trusted Types are not * enabled/supported, or undefined if the policy has not been created yet. */ let policy$1; /** * Returns the Trusted Types policy, or null if Trusted Types are not * enabled/supported. The first call to this function will create the policy. */ function getPolicy$1() { if (policy$1 === undefined) { policy$1 = null; if (_global.trustedTypes) { try { policy$1 = _global.trustedTypes.createPolicy('angular', { createHTML: (s) => s, createScript: (s) => s, createScriptURL: (s) => s, }); } catch { // trustedTypes.createPolicy throws if called with a name that is // already registered, even in report-only mode. Until the API changes, // catch the error not to break the applications functionally. In such // cases, the code will fall back to using strings. } } } return policy$1; } /** * Unsafely promote a string to a TrustedHTML, falling back to strings when * Trusted Types are not available. * @security This is a security-sensitive function; any use of this function * must go through security review. In particular, it must be assured that the * provided string will never cause an XSS vulnerability if used in a context * that will be interpreted as HTML by a browser, e.g. when assigning to * element.innerHTML. */ function trustedHTMLFromString(html) { return getPolicy$1()?.createHTML(html) || html; } /** * Unsafely promote a string to a TrustedScriptURL, falling back to strings * when Trusted Types are not available. * @security This is a security-sensitive function; any use of this function * must go through security review. In particular, it must be assured that the * provided string will never cause an XSS vulnerability if used in a context * that will cause a browser to load and execute a resource, e.g. when * assigning to script.src. */ function trustedScriptURLFromString(url) { return getPolicy$1()?.createScriptURL(url) || url; } /** * Validation function invoked at runtime for each binding that might potentially * represent a security-sensitive attribute of an
* * @param index The index of the container in the data array * @param templateFn Inline template * @param decls The number of nodes, local refs, and pipes for this template * @param vars The number of bindings for this template * @param tagName The name of the container element, if applicable * @param attrsIndex Index of template attributes in the `consts` array. * @param localRefs Index of the local references in the `consts` array. * @param localRefExtractor A function which extracts local-refs values from the template. * Defaults to the current element associated with the local-ref. * * @codeGenApi */ function ɵɵtemplate(index, templateFn, decls, vars, tagName, attrsIndex, localRefsIndex, localRefExtractor) { const lView = getLView(); const tView = getTView(); const adjustedIndex = index + HEADER_OFFSET; const tNode = tView.firstCreatePass ? templateFirstCreatePass(adjustedIndex, tView, lView, templateFn, decls, vars, tagName, attrsIndex, localRefsIndex) : tView.data[adjustedIndex]; setCurrentTNode(tNode, false); const comment = _locateOrCreateContainerAnchor(tView, lView, tNode, index); if (wasLastNodeCreated()) { appendChild(tView, lView, comment, tNode); } attachPatchData(comment, lView); addToViewTree(lView, lView[adjustedIndex] = createLContainer(comment, lView, comment, tNode)); if (isDirectiveHost(tNode)) { createDirectivesInstances(tView, lView, tNode); } if (localRefsIndex != null) { saveResolvedLocalsInData(lView, tNode, localRefExtractor); } } let _locateOrCreateContainerAnchor = createContainerAnchorImpl; /** * Regular creation mode for LContainers and their anchor (comment) nodes. */ function createContainerAnchorImpl(tView, lView, tNode, index) { lastNodeWasCreated(true); return lView[RENDERER].createComment(ngDevMode ? 'container' : ''); } /** * Enables hydration code path (to lookup existing elements in DOM) * in addition to the regular creation mode for LContainers and their * anchor (comment) nodes. */ function locateOrCreateContainerAnchorImpl(tView, lView, tNode, index) { const hydrationInfo = lView[HYDRATION]; const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode$1(hydrationInfo, index); lastNodeWasCreated(isNodeCreationMode); // Regular creation mode. if (isNodeCreationMode) { return createContainerAnchorImpl(tView, lView); } const ssrId = hydrationInfo.data[TEMPLATES]?.[index] ?? null; // Apply `ssrId` value to the underlying TView if it was not previously set. // // There might be situations when the same component is present in a template // multiple times and some instances are opted-out of using hydration via // `ngSkipHydration` attribute. In this scenario, at the time a TView is created, // the `ssrId` might be `null` (if the first component is opted-out of hydration). // The code below makes sure that the `ssrId` is applied to the TView if it's still // `null` and verifies we never try to override it with a different value. if (ssrId !== null && tNode.tView !== null) { if (tNode.tView.ssrId === null) { tNode.tView.ssrId = ssrId; } else { ngDevMode && assertEqual(tNode.tView.ssrId, ssrId, 'Unexpected value of the `ssrId` for this TView'); } } // Hydration mode, looking up existing elements in DOM. const currentRNode = locateNextRNode(hydrationInfo, tView, lView, tNode); ngDevMode && validateNodeExists(currentRNode, lView, tNode); setSegmentHead(hydrationInfo, index, currentRNode); const viewContainerSize = calcSerializedContainerSize(hydrationInfo, index); const comment = siblingAfter(viewContainerSize, currentRNode); if (ngDevMode) { validateMatchingNode(comment, Node.COMMENT_NODE, null, lView, tNode); markRNodeAsClaimedByHydration(comment); } return comment; } function enableLocateOrCreateContainerAnchorImpl() { _locateOrCreateContainerAnchor = locateOrCreateContainerAnchorImpl; } /** Store a value in the `data` at a given `index`. */ function store(tView, lView, index, value) { // We don't store any static data for local variables, so the first time // we see the template, we should store as null to avoid a sparse array if (index >= tView.data.length) { tView.data[index] = null; tView.blueprint[index] = null; } lView[index] = value; } /** * Retrieves a local reference from the current contextViewData. * * If the reference to retrieve is in a parent view, this instruction is used in conjunction * with a nextContext() call, which walks up the tree and updates the contextViewData instance. * * @param index The index of the local ref in contextViewData. * * @codeGenApi */ function ɵɵreference(index) { const contextLView = getContextLView(); return load(contextLView, HEADER_OFFSET + index); } /** * Update a property on a selected element. * * Operates on the element selected by index via the {@link select} instruction. * * If the property name also exists as an input property on one of the element's directives, * the component property will be set instead of the element property. This check must * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled * * @param propName Name of property. Because it is going to DOM, this is not subject to * renaming as part of minification. * @param value New value to write. * @param sanitizer An optional function used to sanitize the value. * @returns This function returns itself so that it may be chained * (e.g. `property('name', ctx.name)('title', ctx.title)`) * * @codeGenApi */ function ɵɵproperty(propName, value, sanitizer) { const lView = getLView(); const bindingIndex = nextBindingIndex(); if (bindingUpdated(lView, bindingIndex, value)) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal(tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, false); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex); } return ɵɵproperty; } /** * Given `
` and `MyDir` with `@Input('style')` we need to write to * directive input. */ function setDirectiveInputsWhichShadowsStyling(tView, tNode, lView, value, isClassBased) { const inputs = tNode.inputs; const property = isClassBased ? 'class' : 'style'; // We support both 'class' and `className` hence the fallback. setInputsForProperty(tView, lView, inputs[property], property, value); } function elementStartFirstCreatePass(index, tView, lView, name, attrsIndex, localRefsIndex) { ngDevMode && assertFirstCreatePass(tView); ngDevMode && ngDevMode.firstCreatePass++; const tViewConsts = tView.consts; const attrs = getConstant(tViewConsts, attrsIndex); const tNode = getOrCreateTNode(tView, index, 2 /* TNodeType.Element */, name, attrs); resolveDirectives(tView, lView, tNode, getConstant(tViewConsts, localRefsIndex)); if (tNode.attrs !== null) { computeStaticStyling(tNode, tNode.attrs, false); } if (tNode.mergedAttrs !== null) { computeStaticStyling(tNode, tNode.mergedAttrs, true); } if (tView.queries !== null) { tView.queries.elementStart(tView, tNode); } return tNode; } /** * Create DOM element. The instruction must later be followed by `elementEnd()` call. * * @param index Index of the element in the LView array * @param name Name of the DOM Node * @param attrsIndex Index of the element's attributes in the `consts` array. * @param localRefsIndex Index of the element's local references in the `consts` array. * @returns This function returns itself so that it may be chained. * * Attributes and localRefs are passed as an array of strings where elements with an even index * hold an attribute name and elements with an odd index hold an attribute value, ex.: * ['id', 'warning5', 'class', 'alert'] * * @codeGenApi */ function ɵɵelementStart(index, name, attrsIndex, localRefsIndex) { const lView = getLView(); const tView = getTView(); const adjustedIndex = HEADER_OFFSET + index; ngDevMode && assertEqual(getBindingIndex(), tView.bindingStartIndex, 'elements should be created before any bindings'); ngDevMode && assertIndexInRange(lView, adjustedIndex); const renderer = lView[RENDERER]; const tNode = tView.firstCreatePass ? elementStartFirstCreatePass(adjustedIndex, tView, lView, name, attrsIndex, localRefsIndex) : tView.data[adjustedIndex]; const native = _locateOrCreateElementNode(tView, lView, tNode, renderer, name, index); lView[adjustedIndex] = native; const hasDirectives = isDirectiveHost(tNode); if (ngDevMode && tView.firstCreatePass) { validateElementIsKnown(native, lView, tNode.value, tView.schemas, hasDirectives); } setCurrentTNode(tNode, true); setupStaticAttributes(renderer, native, tNode); if ((tNode.flags & 32 /* TNodeFlags.isDetached */) !== 32 /* TNodeFlags.isDetached */ && wasLastNodeCreated()) { // In the i18n case, the translation may have removed this element, so only add it if it is not // detached. See `TNodeType.Placeholder` and `LFrame.inI18n` for more context. appendChild(tView, lView, native, tNode); } // any immediate children of a component or template container must be pre-emptively // monkey-patched with the component view data so that the element can be inspected // later on using any element discovery utility methods (see `element_discovery.ts`) if (getElementDepthCount() === 0) { attachPatchData(native, lView); } increaseElementDepthCount(); if (hasDirectives) { createDirectivesInstances(tView, lView, tNode); executeContentQueries(tView, tNode, lView); } if (localRefsIndex !== null) { saveResolvedLocalsInData(lView, tNode); } return ɵɵelementStart; } /** * Mark the end of the element. * @returns This function returns itself so that it may be chained. * * @codeGenApi */ function ɵɵelementEnd() { let currentTNode = getCurrentTNode(); ngDevMode && assertDefined(currentTNode, 'No parent node to close.'); if (isCurrentTNodeParent()) { setCurrentTNodeAsNotParent(); } else { ngDevMode && assertHasParent(getCurrentTNode()); currentTNode = currentTNode.parent; setCurrentTNode(currentTNode, false); } const tNode = currentTNode; ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */); if (isSkipHydrationRootTNode(tNode)) { leaveSkipHydrationBlock(); } decreaseElementDepthCount(); const tView = getTView(); if (tView.firstCreatePass) { registerPostOrderHooks(tView, currentTNode); if (isContentQueryHost(currentTNode)) { tView.queries.elementEnd(currentTNode); } } if (tNode.classesWithoutHost != null && hasClassInput(tNode)) { setDirectiveInputsWhichShadowsStyling(tView, tNode, getLView(), tNode.classesWithoutHost, true); } if (tNode.stylesWithoutHost != null && hasStyleInput(tNode)) { setDirectiveInputsWhichShadowsStyling(tView, tNode, getLView(), tNode.stylesWithoutHost, false); } return ɵɵelementEnd; } /** * Creates an empty element using {@link elementStart} and {@link elementEnd} * * @param index Index of the element in the data array * @param name Name of the DOM Node * @param attrsIndex Index of the element's attributes in the `consts` array. * @param localRefsIndex Index of the element's local references in the `consts` array. * @returns This function returns itself so that it may be chained. * * @codeGenApi */ function ɵɵelement(index, name, attrsIndex, localRefsIndex) { ɵɵelementStart(index, name, attrsIndex, localRefsIndex); ɵɵelementEnd(); return ɵɵelement; } let _locateOrCreateElementNode = (tView, lView, tNode, renderer, name, index) => { lastNodeWasCreated(true); return createElementNode(renderer, name, getNamespace$1()); }; /** * Enables hydration code path (to lookup existing elements in DOM) * in addition to the regular creation mode of element nodes. */ function locateOrCreateElementNodeImpl(tView, lView, tNode, renderer, name, index) { const hydrationInfo = lView[HYDRATION]; const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode$1(hydrationInfo, index); lastNodeWasCreated(isNodeCreationMode); // Regular creation mode. if (isNodeCreationMode) { return createElementNode(renderer, name, getNamespace$1()); } // Hydration mode, looking up an existing element in DOM. const native = locateNextRNode(hydrationInfo, tView, lView, tNode); ngDevMode && validateMatchingNode(native, Node.ELEMENT_NODE, name, lView, tNode); ngDevMode && markRNodeAsClaimedByHydration(native); // This element might also be an anchor of a view container. if (getSerializedContainerViews(hydrationInfo, index)) { // Important note: this element acts as an anchor, but it's **not** a part // of the embedded view, so we start the segment **after** this element, taking // a reference to the next sibling. For example, the following template: // `
` is represented in the DOM as `
...`, // so while processing a `
` instruction, point to the next sibling as a // start of a segment. ngDevMode && validateNodeExists(native.nextSibling, lView, tNode); setSegmentHead(hydrationInfo, index, native.nextSibling); } // Checks if the skip hydration attribute is present during hydration so we know to // skip attempting to hydrate this block. if (hydrationInfo && hasNgSkipHydrationAttr(tNode)) { if (isComponentHost(tNode)) { enterSkipHydrationBlock(tNode); // Since this isn't hydratable, we need to empty the node // so there's no duplicate content after render clearElementContents(native); ngDevMode && ngDevMode.componentsSkippedHydration++; } else if (ngDevMode) { // If this is not a component host, throw an error. // Hydration can be skipped on per-component basis only. throw invalidSkipHydrationHost(native); } } return native; } function enableLocateOrCreateElementNodeImpl() { _locateOrCreateElementNode = locateOrCreateElementNodeImpl; } function elementContainerStartFirstCreatePass(index, tView, lView, attrsIndex, localRefsIndex) { ngDevMode && ngDevMode.firstCreatePass++; const tViewConsts = tView.consts; const attrs = getConstant(tViewConsts, attrsIndex); const tNode = getOrCreateTNode(tView, index, 8 /* TNodeType.ElementContainer */, 'ng-container', attrs); // While ng-container doesn't necessarily support styling, we use the style context to identify // and execute directives on the ng-container. if (attrs !== null) { computeStaticStyling(tNode, attrs, true); } const localRefs = getConstant(tViewConsts, localRefsIndex); resolveDirectives(tView, lView, tNode, localRefs); if (tView.queries !== null) { tView.queries.elementStart(tView, tNode); } return tNode; } /** * Creates a logical container for other nodes () backed by a comment node in the DOM. * The instruction must later be followed by `elementContainerEnd()` call. * * @param index Index of the element in the LView array * @param attrsIndex Index of the container attributes in the `consts` array. * @param localRefsIndex Index of the container's local references in the `consts` array. * @returns This function returns itself so that it may be chained. * * Even if this instruction accepts a set of attributes no actual attribute values are propagated to * the DOM (as a comment node can't have attributes). Attributes are here only for directive * matching purposes and setting initial inputs of directives. * * @codeGenApi */ function ɵɵelementContainerStart(index, attrsIndex, localRefsIndex) { const lView = getLView(); const tView = getTView(); const adjustedIndex = index + HEADER_OFFSET; ngDevMode && assertIndexInRange(lView, adjustedIndex); ngDevMode && assertEqual(getBindingIndex(), tView.bindingStartIndex, 'element containers should be created before any bindings'); const tNode = tView.firstCreatePass ? elementContainerStartFirstCreatePass(adjustedIndex, tView, lView, attrsIndex, localRefsIndex) : tView.data[adjustedIndex]; setCurrentTNode(tNode, true); const comment = _locateOrCreateElementContainerNode(tView, lView, tNode, index); lView[adjustedIndex] = comment; if (wasLastNodeCreated()) { appendChild(tView, lView, comment, tNode); } attachPatchData(comment, lView); if (isDirectiveHost(tNode)) { createDirectivesInstances(tView, lView, tNode); executeContentQueries(tView, tNode, lView); } if (localRefsIndex != null) { saveResolvedLocalsInData(lView, tNode); } return ɵɵelementContainerStart; } /** * Mark the end of the . * @returns This function returns itself so that it may be chained. * * @codeGenApi */ function ɵɵelementContainerEnd() { let currentTNode = getCurrentTNode(); const tView = getTView(); if (isCurrentTNodeParent()) { setCurrentTNodeAsNotParent(); } else { ngDevMode && assertHasParent(currentTNode); currentTNode = currentTNode.parent; setCurrentTNode(currentTNode, false); } ngDevMode && assertTNodeType(currentTNode, 8 /* TNodeType.ElementContainer */); if (tView.firstCreatePass) { registerPostOrderHooks(tView, currentTNode); if (isContentQueryHost(currentTNode)) { tView.queries.elementEnd(currentTNode); } } return ɵɵelementContainerEnd; } /** * Creates an empty logical container using {@link elementContainerStart} * and {@link elementContainerEnd} * * @param index Index of the element in the LView array * @param attrsIndex Index of the container attributes in the `consts` array. * @param localRefsIndex Index of the container's local references in the `consts` array. * @returns This function returns itself so that it may be chained. * * @codeGenApi */ function ɵɵelementContainer(index, attrsIndex, localRefsIndex) { ɵɵelementContainerStart(index, attrsIndex, localRefsIndex); ɵɵelementContainerEnd(); return ɵɵelementContainer; } let _locateOrCreateElementContainerNode = (tView, lView, tNode, index) => { lastNodeWasCreated(true); return createCommentNode(lView[RENDERER], ngDevMode ? 'ng-container' : ''); }; /** * Enables hydration code path (to lookup existing elements in DOM) * in addition to the regular creation mode of comment nodes that * represent 's anchor. */ function locateOrCreateElementContainerNode(tView, lView, tNode, index) { let comment; const hydrationInfo = lView[HYDRATION]; const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1(); lastNodeWasCreated(isNodeCreationMode); // Regular creation mode. if (isNodeCreationMode) { return createCommentNode(lView[RENDERER], ngDevMode ? 'ng-container' : ''); } // Hydration mode, looking up existing elements in DOM. const currentRNode = locateNextRNode(hydrationInfo, tView, lView, tNode); ngDevMode && validateNodeExists(currentRNode, lView, tNode); const ngContainerSize = getNgContainerSize(hydrationInfo, index); ngDevMode && assertNumber(ngContainerSize, 'Unexpected state: hydrating an , ' + 'but no hydration info is available.'); setSegmentHead(hydrationInfo, index, currentRNode); comment = siblingAfter(ngContainerSize, currentRNode); if (ngDevMode) { validateMatchingNode(comment, Node.COMMENT_NODE, null, lView, tNode); markRNodeAsClaimedByHydration(comment); } return comment; } function enableLocateOrCreateElementContainerNodeImpl() { _locateOrCreateElementContainerNode = locateOrCreateElementContainerNode; } /** * Returns the current OpaqueViewState instance. * * Used in conjunction with the restoreView() instruction to save a snapshot * of the current view and restore it when listeners are invoked. This allows * walking the declaration view tree in listeners to get vars from parent views. * * @codeGenApi */ function ɵɵgetCurrentView() { return getLView(); } /** * Determine if the argument is shaped like a Promise */ function isPromise(obj) { // allow any Promise/A+ compliant thenable. // It's up to the caller to ensure that obj.then conforms to the spec return !!obj && typeof obj.then === 'function'; } /** * Determine if the argument is a Subscribable */ function isSubscribable(obj) { return !!obj && typeof obj.subscribe === 'function'; } /** * Adds an event listener to the current node. * * If an output exists on one of the node's directives, it also subscribes to the output * and saves the subscription for later cleanup. * * @param eventName Name of the event * @param listenerFn The function to be called when event emits * @param useCapture Whether or not to use capture in event listener - this argument is a reminder * from the Renderer3 infrastructure and should be removed from the instruction arguments * @param eventTargetResolver Function that returns global target information in case this listener * should be attached to a global object like window, document or body * * @codeGenApi */ function ɵɵlistener(eventName, listenerFn, useCapture, eventTargetResolver) { const lView = getLView(); const tView = getTView(); const tNode = getCurrentTNode(); listenerInternal(tView, lView, lView[RENDERER], tNode, eventName, listenerFn, eventTargetResolver); return ɵɵlistener; } /** * Registers a synthetic host listener (e.g. `(@foo.start)`) on a component or directive. * * This instruction is for compatibility purposes and is designed to ensure that a * synthetic host listener (e.g. `@HostListener('@foo.start')`) properly gets rendered * in the component's renderer. Normally all host listeners are evaluated with the * parent component's renderer, but, in the case of animation @triggers, they need * to be evaluated with the sub component's renderer (because that's where the * animation triggers are defined). * * Do not use this instruction as a replacement for `listener`. This instruction * only exists to ensure compatibility with the ViewEngine's host binding behavior. * * @param eventName Name of the event * @param listenerFn The function to be called when event emits * @param useCapture Whether or not to use capture in event listener * @param eventTargetResolver Function that returns global target information in case this listener * should be attached to a global object like window, document or body * * @codeGenApi */ function ɵɵsyntheticHostListener(eventName, listenerFn) { const tNode = getCurrentTNode(); const lView = getLView(); const tView = getTView(); const currentDef = getCurrentDirectiveDef(tView.data); const renderer = loadComponentRenderer(currentDef, tNode, lView); listenerInternal(tView, lView, renderer, tNode, eventName, listenerFn); return ɵɵsyntheticHostListener; } /** * A utility function that checks if a given element has already an event handler registered for an * event with a specified name. The TView.cleanup data structure is used to find out which events * are registered for a given element. */ function findExistingListener(tView, lView, eventName, tNodeIdx) { const tCleanup = tView.cleanup; if (tCleanup != null) { for (let i = 0; i < tCleanup.length - 1; i += 2) { const cleanupEventName = tCleanup[i]; if (cleanupEventName === eventName && tCleanup[i + 1] === tNodeIdx) { // We have found a matching event name on the same node but it might not have been // registered yet, so we must explicitly verify entries in the LView cleanup data // structures. const lCleanup = lView[CLEANUP]; const listenerIdxInLCleanup = tCleanup[i + 2]; return lCleanup.length > listenerIdxInLCleanup ? lCleanup[listenerIdxInLCleanup] : null; } // TView.cleanup can have a mix of 4-elements entries (for event handler cleanups) or // 2-element entries (for directive and queries destroy hooks). As such we can encounter // blocks of 4 or 2 items in the tView.cleanup and this is why we iterate over 2 elements // first and jump another 2 elements if we detect listeners cleanup (4 elements). Also check // documentation of TView.cleanup for more details of this data structure layout. if (typeof cleanupEventName === 'string') { i += 2; } } } return null; } function listenerInternal(tView, lView, renderer, tNode, eventName, listenerFn, eventTargetResolver) { const isTNodeDirectiveHost = isDirectiveHost(tNode); const firstCreatePass = tView.firstCreatePass; const tCleanup = firstCreatePass && getOrCreateTViewCleanup(tView); const context = lView[CONTEXT]; // When the ɵɵlistener instruction was generated and is executed we know that there is either a // native listener or a directive output on this element. As such we we know that we will have to // register a listener and store its cleanup function on LView. const lCleanup = getOrCreateLViewCleanup(lView); ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */); let processOutputs = true; // Adding a native event listener is applicable when: // - The corresponding TNode represents a DOM element. // - The event target has a resolver (usually resulting in a global object, // such as `window` or `document`). if ((tNode.type & 3 /* TNodeType.AnyRNode */) || eventTargetResolver) { const native = getNativeByTNode(tNode, lView); const target = eventTargetResolver ? eventTargetResolver(native) : native; const lCleanupIndex = lCleanup.length; const idxOrTargetGetter = eventTargetResolver ? (_lView) => eventTargetResolver(unwrapRNode(_lView[tNode.index])) : tNode.index; // In order to match current behavior, native DOM event listeners must be added for all // events (including outputs). // There might be cases where multiple directives on the same element try to register an event // handler function for the same event. In this situation we want to avoid registration of // several native listeners as each registration would be intercepted by NgZone and // trigger change detection. This would mean that a single user action would result in several // change detections being invoked. To avoid this situation we want to have only one call to // native handler registration (for the same element and same type of event). // // In order to have just one native event handler in presence of multiple handler functions, // we just register a first handler function as a native event listener and then chain // (coalesce) other handler functions on top of the first native handler function. let existingListener = null; // Please note that the coalescing described here doesn't happen for events specifying an // alternative target (ex. (document:click)) - this is to keep backward compatibility with the // view engine. // Also, we don't have to search for existing listeners is there are no directives // matching on a given node as we can't register multiple event handlers for the same event in // a template (this would mean having duplicate attributes). if (!eventTargetResolver && isTNodeDirectiveHost) { existingListener = findExistingListener(tView, lView, eventName, tNode.index); } if (existingListener !== null) { // Attach a new listener to coalesced listeners list, maintaining the order in which // listeners are registered. For performance reasons, we keep a reference to the last // listener in that list (in `__ngLastListenerFn__` field), so we can avoid going through // the entire set each time we need to add a new listener. const lastListenerFn = existingListener.__ngLastListenerFn__ || existingListener; lastListenerFn.__ngNextListenerFn__ = listenerFn; existingListener.__ngLastListenerFn__ = listenerFn; processOutputs = false; } else { listenerFn = wrapListener(tNode, lView, context, listenerFn, false /** preventDefault */); const cleanupFn = renderer.listen(target, eventName, listenerFn); ngDevMode && ngDevMode.rendererAddEventListener++; lCleanup.push(listenerFn, cleanupFn); tCleanup && tCleanup.push(eventName, idxOrTargetGetter, lCleanupIndex, lCleanupIndex + 1); } } else { // Even if there is no native listener to add, we still need to wrap the listener so that OnPush // ancestors are marked dirty when an event occurs. listenerFn = wrapListener(tNode, lView, context, listenerFn, false /** preventDefault */); } // subscribe to directive outputs const outputs = tNode.outputs; let props; if (processOutputs && outputs !== null && (props = outputs[eventName])) { const propsLength = props.length; if (propsLength) { for (let i = 0; i < propsLength; i += 2) { const index = props[i]; ngDevMode && assertIndexInRange(lView, index); const minifiedName = props[i + 1]; const directiveInstance = lView[index]; const output = directiveInstance[minifiedName]; if (ngDevMode && !isSubscribable(output)) { throw new Error(`@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`); } const subscription = output.subscribe(listenerFn); const idx = lCleanup.length; lCleanup.push(listenerFn, subscription); tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1)); } } } } function executeListenerWithErrorHandling(lView, context, listenerFn, e) { try { profiler(6 /* ProfilerEvent.OutputStart */, context, listenerFn); // Only explicitly returning false from a listener should preventDefault return listenerFn(e) !== false; } catch (error) { handleError(lView, error); return false; } finally { profiler(7 /* ProfilerEvent.OutputEnd */, context, listenerFn); } } /** * Wraps an event listener with a function that marks ancestors dirty and prevents default behavior, * if applicable. * * @param tNode The TNode associated with this listener * @param lView The LView that contains this listener * @param listenerFn The listener function to call * @param wrapWithPreventDefault Whether or not to prevent default behavior * (the procedural renderer does this already, so in those cases, we should skip) */ function wrapListener(tNode, lView, context, listenerFn, wrapWithPreventDefault) { // Note: we are performing most of the work in the listener function itself // to optimize listener registration. return function wrapListenerIn_markDirtyAndPreventDefault(e) { // Ivy uses `Function` as a special token that allows us to unwrap the function // so that it can be invoked programmatically by `DebugNode.triggerEventHandler`. if (e === Function) { return listenerFn; } // In order to be backwards compatible with View Engine, events on component host nodes // must also mark the component view itself dirty (i.e. the view that it owns). const startView = tNode.componentOffset > -1 ? getComponentLViewByIndex(tNode.index, lView) : lView; markViewDirty(startView); let result = executeListenerWithErrorHandling(lView, context, listenerFn, e); // A just-invoked listener function might have coalesced listeners so we need to check for // their presence and invoke as needed. let nextListenerFn = wrapListenerIn_markDirtyAndPreventDefault.__ngNextListenerFn__; while (nextListenerFn) { // We should prevent default if any of the listeners explicitly return false result = executeListenerWithErrorHandling(lView, context, nextListenerFn, e) && result; nextListenerFn = nextListenerFn.__ngNextListenerFn__; } if (wrapWithPreventDefault && result === false) { e.preventDefault(); } return result; }; } /** * Retrieves a context at the level specified and saves it as the global, contextViewData. * Will get the next level up if level is not specified. * * This is used to save contexts of parent views so they can be bound in embedded views, or * in conjunction with reference() to bind a ref from a parent view. * * @param level The relative level of the view from which to grab context compared to contextVewData * @returns context * * @codeGenApi */ function ɵɵnextContext(level = 1) { return nextContextImpl(level); } /** * Checks a given node against matching projection slots and returns the * determined slot index. Returns "null" if no slot matched the given node. * * This function takes into account the parsed ngProjectAs selector from the * node's attributes. If present, it will check whether the ngProjectAs selector * matches any of the projection slot selectors. */ function matchingProjectionSlotIndex(tNode, projectionSlots) { let wildcardNgContentIndex = null; const ngProjectAsAttrVal = getProjectAsAttrValue(tNode); for (let i = 0; i < projectionSlots.length; i++) { const slotValue = projectionSlots[i]; // The last wildcard projection slot should match all nodes which aren't matching // any selector. This is necessary to be backwards compatible with view engine. if (slotValue === '*') { wildcardNgContentIndex = i; continue; } // If we ran into an `ngProjectAs` attribute, we should match its parsed selector // to the list of selectors, otherwise we fall back to matching against the node. if (ngProjectAsAttrVal === null ? isNodeMatchingSelectorList(tNode, slotValue, /* isProjectionMode */ true) : isSelectorInSelectorList(ngProjectAsAttrVal, slotValue)) { return i; // first matching selector "captures" a given node } } return wildcardNgContentIndex; } /** * Instruction to distribute projectable nodes among occurrences in a given template. * It takes all the selectors from the entire component's template and decides where * each projected node belongs (it re-distributes nodes among "buckets" where each "bucket" is * backed by a selector). * * This function requires CSS selectors to be provided in 2 forms: parsed (by a compiler) and text, * un-parsed form. * * The parsed form is needed for efficient matching of a node against a given CSS selector. * The un-parsed, textual form is needed for support of the ngProjectAs attribute. * * Having a CSS selector in 2 different formats is not ideal, but alternatives have even more * drawbacks: * - having only a textual form would require runtime parsing of CSS selectors; * - we can't have only a parsed as we can't re-construct textual form from it (as entered by a * template author). * * @param projectionSlots? A collection of projection slots. A projection slot can be based * on a parsed CSS selectors or set to the wildcard selector ("*") in order to match * all nodes which do not match any selector. If not specified, a single wildcard * selector projection slot will be defined. * * @codeGenApi */ function ɵɵprojectionDef(projectionSlots) { const componentNode = getLView()[DECLARATION_COMPONENT_VIEW][T_HOST]; if (!componentNode.projection) { // If no explicit projection slots are defined, fall back to a single // projection slot with the wildcard selector. const numProjectionSlots = projectionSlots ? projectionSlots.length : 1; const projectionHeads = componentNode.projection = newArray(numProjectionSlots, null); const tails = projectionHeads.slice(); let componentChild = componentNode.child; while (componentChild !== null) { const slotIndex = projectionSlots ? matchingProjectionSlotIndex(componentChild, projectionSlots) : 0; if (slotIndex !== null) { if (tails[slotIndex]) { tails[slotIndex].projectionNext = componentChild; } else { projectionHeads[slotIndex] = componentChild; } tails[slotIndex] = componentChild; } componentChild = componentChild.next; } } } /** * Inserts previously re-distributed projected nodes. This instruction must be preceded by a call * to the projectionDef instruction. * * @param nodeIndex * @param selectorIndex: * - 0 when the selector is `*` (or unspecified as this is the default value), * - 1 based index of the selector from the {@link projectionDef} * * @codeGenApi */ function ɵɵprojection(nodeIndex, selectorIndex = 0, attrs) { const lView = getLView(); const tView = getTView(); const tProjectionNode = getOrCreateTNode(tView, HEADER_OFFSET + nodeIndex, 16 /* TNodeType.Projection */, null, attrs || null); // We can't use viewData[HOST_NODE] because projection nodes can be nested in embedded views. if (tProjectionNode.projection === null) tProjectionNode.projection = selectorIndex; // `` has no content setCurrentTNodeAsNotParent(); const hydrationInfo = lView[HYDRATION]; const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1(); if (isNodeCreationMode && (tProjectionNode.flags & 32 /* TNodeFlags.isDetached */) !== 32 /* TNodeFlags.isDetached */) { // re-distribution of projectable nodes is stored on a component's view level applyProjection(tView, lView, tProjectionNode); } } /** * * Update an interpolated property on an element with a lone bound value * * Used when the value passed to a property has 1 interpolated value in it, an no additional text * surrounds that interpolated value: * * ```html *
* ``` * * Its compiled representation is:: * * ```ts * ɵɵpropertyInterpolate('title', v0); * ``` * * If the property name also exists as an input property on one of the element's directives, * the component property will be set instead of the element property. This check must * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled. * * @param propName The name of the property to update * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param suffix Static value used for concatenation only. * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵpropertyInterpolate(propName, v0, sanitizer) { ɵɵpropertyInterpolate1(propName, '', v0, '', sanitizer); return ɵɵpropertyInterpolate; } /** * * Update an interpolated property on an element with single bound value surrounded by text. * * Used when the value passed to a property has 1 interpolated value in it: * * ```html *
* ``` * * Its compiled representation is:: * * ```ts * ɵɵpropertyInterpolate1('title', 'prefix', v0, 'suffix'); * ``` * * If the property name also exists as an input property on one of the element's directives, * the component property will be set instead of the element property. This check must * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled. * * @param propName The name of the property to update * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param suffix Static value used for concatenation only. * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵpropertyInterpolate1(propName, prefix, v0, suffix, sanitizer) { const lView = getLView(); const interpolatedValue = interpolation1(lView, prefix, v0, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 1, prefix, suffix); } return ɵɵpropertyInterpolate1; } /** * * Update an interpolated property on an element with 2 bound values surrounded by text. * * Used when the value passed to a property has 2 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is:: * * ```ts * ɵɵpropertyInterpolate2('title', 'prefix', v0, '-', v1, 'suffix'); * ``` * * If the property name also exists as an input property on one of the element's directives, * the component property will be set instead of the element property. This check must * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled. * * @param propName The name of the property to update * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param suffix Static value used for concatenation only. * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵpropertyInterpolate2(propName, prefix, v0, i0, v1, suffix, sanitizer) { const lView = getLView(); const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 2, prefix, i0, suffix); } return ɵɵpropertyInterpolate2; } /** * * Update an interpolated property on an element with 3 bound values surrounded by text. * * Used when the value passed to a property has 3 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is:: * * ```ts * ɵɵpropertyInterpolate3( * 'title', 'prefix', v0, '-', v1, '-', v2, 'suffix'); * ``` * * If the property name also exists as an input property on one of the element's directives, * the component property will be set instead of the element property. This check must * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled. * * @param propName The name of the property to update * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param suffix Static value used for concatenation only. * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵpropertyInterpolate3(propName, prefix, v0, i0, v1, i1, v2, suffix, sanitizer) { const lView = getLView(); const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 3, prefix, i0, i1, suffix); } return ɵɵpropertyInterpolate3; } /** * * Update an interpolated property on an element with 4 bound values surrounded by text. * * Used when the value passed to a property has 4 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is:: * * ```ts * ɵɵpropertyInterpolate4( * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix'); * ``` * * If the property name also exists as an input property on one of the element's directives, * the component property will be set instead of the element property. This check must * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled. * * @param propName The name of the property to update * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param suffix Static value used for concatenation only. * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵpropertyInterpolate4(propName, prefix, v0, i0, v1, i1, v2, i2, v3, suffix, sanitizer) { const lView = getLView(); const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 4, prefix, i0, i1, i2, suffix); } return ɵɵpropertyInterpolate4; } /** * * Update an interpolated property on an element with 5 bound values surrounded by text. * * Used when the value passed to a property has 5 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is:: * * ```ts * ɵɵpropertyInterpolate5( * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix'); * ``` * * If the property name also exists as an input property on one of the element's directives, * the component property will be set instead of the element property. This check must * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled. * * @param propName The name of the property to update * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param suffix Static value used for concatenation only. * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵpropertyInterpolate5(propName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix, sanitizer) { const lView = getLView(); const interpolatedValue = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 5, prefix, i0, i1, i2, i3, suffix); } return ɵɵpropertyInterpolate5; } /** * * Update an interpolated property on an element with 6 bound values surrounded by text. * * Used when the value passed to a property has 6 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is:: * * ```ts * ɵɵpropertyInterpolate6( * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix'); * ``` * * If the property name also exists as an input property on one of the element's directives, * the component property will be set instead of the element property. This check must * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled. * * @param propName The name of the property to update * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param suffix Static value used for concatenation only. * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵpropertyInterpolate6(propName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix, sanitizer) { const lView = getLView(); const interpolatedValue = interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 6, prefix, i0, i1, i2, i3, i4, suffix); } return ɵɵpropertyInterpolate6; } /** * * Update an interpolated property on an element with 7 bound values surrounded by text. * * Used when the value passed to a property has 7 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is:: * * ```ts * ɵɵpropertyInterpolate7( * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix'); * ``` * * If the property name also exists as an input property on one of the element's directives, * the component property will be set instead of the element property. This check must * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled. * * @param propName The name of the property to update * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param i5 Static value used for concatenation only. * @param v6 Value checked for change. * @param suffix Static value used for concatenation only. * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵpropertyInterpolate7(propName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix, sanitizer) { const lView = getLView(); const interpolatedValue = interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 7, prefix, i0, i1, i2, i3, i4, i5, suffix); } return ɵɵpropertyInterpolate7; } /** * * Update an interpolated property on an element with 8 bound values surrounded by text. * * Used when the value passed to a property has 8 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is:: * * ```ts * ɵɵpropertyInterpolate8( * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, 'suffix'); * ``` * * If the property name also exists as an input property on one of the element's directives, * the component property will be set instead of the element property. This check must * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled. * * @param propName The name of the property to update * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param i5 Static value used for concatenation only. * @param v6 Value checked for change. * @param i6 Static value used for concatenation only. * @param v7 Value checked for change. * @param suffix Static value used for concatenation only. * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵpropertyInterpolate8(propName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix, sanitizer) { const lView = getLView(); const interpolatedValue = interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 8, prefix, i0, i1, i2, i3, i4, i5, i6, suffix); } return ɵɵpropertyInterpolate8; } /** * Update an interpolated property on an element with 9 or more bound values surrounded by text. * * Used when the number of interpolated values exceeds 8. * * ```html *
* ``` * * Its compiled representation is:: * * ```ts * ɵɵpropertyInterpolateV( * 'title', ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9, * 'suffix']); * ``` * * If the property name also exists as an input property on one of the element's directives, * the component property will be set instead of the element property. This check must * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled. * * @param propName The name of the property to update. * @param values The collection of values and the strings in between those values, beginning with a * string prefix and ending with a string suffix. * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`) * @param sanitizer An optional sanitizer function * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵpropertyInterpolateV(propName, values, sanitizer) { const lView = getLView(); const interpolatedValue = interpolationV(lView, values); if (interpolatedValue !== NO_CHANGE) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false); if (ngDevMode) { const interpolationInBetween = [values[0]]; // prefix for (let i = 2; i < values.length; i += 2) { interpolationInBetween.push(values[i]); } storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - interpolationInBetween.length + 1, ...interpolationInBetween); } } return ɵɵpropertyInterpolateV; } function toTStylingRange(prev, next) { ngDevMode && assertNumberInRange(prev, 0, 32767 /* StylingRange.UNSIGNED_MASK */); ngDevMode && assertNumberInRange(next, 0, 32767 /* StylingRange.UNSIGNED_MASK */); return (prev << 17 /* StylingRange.PREV_SHIFT */ | next << 2 /* StylingRange.NEXT_SHIFT */); } function getTStylingRangePrev(tStylingRange) { ngDevMode && assertNumber(tStylingRange, 'expected number'); return (tStylingRange >> 17 /* StylingRange.PREV_SHIFT */) & 32767 /* StylingRange.UNSIGNED_MASK */; } function getTStylingRangePrevDuplicate(tStylingRange) { ngDevMode && assertNumber(tStylingRange, 'expected number'); return (tStylingRange & 2 /* StylingRange.PREV_DUPLICATE */) == 2 /* StylingRange.PREV_DUPLICATE */; } function setTStylingRangePrev(tStylingRange, previous) { ngDevMode && assertNumber(tStylingRange, 'expected number'); ngDevMode && assertNumberInRange(previous, 0, 32767 /* StylingRange.UNSIGNED_MASK */); return ((tStylingRange & ~4294836224 /* StylingRange.PREV_MASK */) | (previous << 17 /* StylingRange.PREV_SHIFT */)); } function setTStylingRangePrevDuplicate(tStylingRange) { ngDevMode && assertNumber(tStylingRange, 'expected number'); return (tStylingRange | 2 /* StylingRange.PREV_DUPLICATE */); } function getTStylingRangeNext(tStylingRange) { ngDevMode && assertNumber(tStylingRange, 'expected number'); return (tStylingRange & 131068 /* StylingRange.NEXT_MASK */) >> 2 /* StylingRange.NEXT_SHIFT */; } function setTStylingRangeNext(tStylingRange, next) { ngDevMode && assertNumber(tStylingRange, 'expected number'); ngDevMode && assertNumberInRange(next, 0, 32767 /* StylingRange.UNSIGNED_MASK */); return ((tStylingRange & ~131068 /* StylingRange.NEXT_MASK */) | // next << 2 /* StylingRange.NEXT_SHIFT */); } function getTStylingRangeNextDuplicate(tStylingRange) { ngDevMode && assertNumber(tStylingRange, 'expected number'); return (tStylingRange & 1 /* StylingRange.NEXT_DUPLICATE */) === 1 /* StylingRange.NEXT_DUPLICATE */; } function setTStylingRangeNextDuplicate(tStylingRange) { ngDevMode && assertNumber(tStylingRange, 'expected number'); return (tStylingRange | 1 /* StylingRange.NEXT_DUPLICATE */); } /** * Insert new `tStyleValue` at `TData` and link existing style bindings such that we maintain linked * list of styles and compute the duplicate flag. * * Note: this function is executed during `firstUpdatePass` only to populate the `TView.data`. * * The function works by keeping track of `tStylingRange` which contains two pointers pointing to * the head/tail of the template portion of the styles. * - if `isHost === false` (we are template) then insertion is at tail of `TStylingRange` * - if `isHost === true` (we are host binding) then insertion is at head of `TStylingRange` * * @param tData The `TData` to insert into. * @param tNode `TNode` associated with the styling element. * @param tStylingKey See `TStylingKey`. * @param index location of where `tStyleValue` should be stored (and linked into list.) * @param isHostBinding `true` if the insertion is for a `hostBinding`. (insertion is in front of * template.) * @param isClassBinding True if the associated `tStylingKey` as a `class` styling. * `tNode.classBindings` should be used (or `tNode.styleBindings` otherwise.) */ function insertTStylingBinding(tData, tNode, tStylingKeyWithStatic, index, isHostBinding, isClassBinding) { ngDevMode && assertFirstUpdatePass(getTView()); let tBindings = isClassBinding ? tNode.classBindings : tNode.styleBindings; let tmplHead = getTStylingRangePrev(tBindings); let tmplTail = getTStylingRangeNext(tBindings); tData[index] = tStylingKeyWithStatic; let isKeyDuplicateOfStatic = false; let tStylingKey; if (Array.isArray(tStylingKeyWithStatic)) { // We are case when the `TStylingKey` contains static fields as well. const staticKeyValueArray = tStylingKeyWithStatic; tStylingKey = staticKeyValueArray[1]; // unwrap. // We need to check if our key is present in the static so that we can mark it as duplicate. if (tStylingKey === null || keyValueArrayIndexOf(staticKeyValueArray, tStylingKey) > 0) { // tStylingKey is present in the statics, need to mark it as duplicate. isKeyDuplicateOfStatic = true; } } else { tStylingKey = tStylingKeyWithStatic; } if (isHostBinding) { // We are inserting host bindings // If we don't have template bindings then `tail` is 0. const hasTemplateBindings = tmplTail !== 0; // This is important to know because that means that the `head` can't point to the first // template bindings (there are none.) Instead the head points to the tail of the template. if (hasTemplateBindings) { // template head's "prev" will point to last host binding or to 0 if no host bindings yet const previousNode = getTStylingRangePrev(tData[tmplHead + 1]); tData[index + 1] = toTStylingRange(previousNode, tmplHead); // if a host binding has already been registered, we need to update the next of that host // binding to point to this one if (previousNode !== 0) { // We need to update the template-tail value to point to us. tData[previousNode + 1] = setTStylingRangeNext(tData[previousNode + 1], index); } // The "previous" of the template binding head should point to this host binding tData[tmplHead + 1] = setTStylingRangePrev(tData[tmplHead + 1], index); } else { tData[index + 1] = toTStylingRange(tmplHead, 0); // if a host binding has already been registered, we need to update the next of that host // binding to point to this one if (tmplHead !== 0) { // We need to update the template-tail value to point to us. tData[tmplHead + 1] = setTStylingRangeNext(tData[tmplHead + 1], index); } // if we don't have template, the head points to template-tail, and needs to be advanced. tmplHead = index; } } else { // We are inserting in template section. // We need to set this binding's "previous" to the current template tail tData[index + 1] = toTStylingRange(tmplTail, 0); ngDevMode && assertEqual(tmplHead !== 0 && tmplTail === 0, false, 'Adding template bindings after hostBindings is not allowed.'); if (tmplHead === 0) { tmplHead = index; } else { // We need to update the previous value "next" to point to this binding tData[tmplTail + 1] = setTStylingRangeNext(tData[tmplTail + 1], index); } tmplTail = index; } // Now we need to update / compute the duplicates. // Starting with our location search towards head (least priority) if (isKeyDuplicateOfStatic) { tData[index + 1] = setTStylingRangePrevDuplicate(tData[index + 1]); } markDuplicates(tData, tStylingKey, index, true); markDuplicates(tData, tStylingKey, index, false); markDuplicateOfResidualStyling(tNode, tStylingKey, tData, index, isClassBinding); tBindings = toTStylingRange(tmplHead, tmplTail); if (isClassBinding) { tNode.classBindings = tBindings; } else { tNode.styleBindings = tBindings; } } /** * Look into the residual styling to see if the current `tStylingKey` is duplicate of residual. * * @param tNode `TNode` where the residual is stored. * @param tStylingKey `TStylingKey` to store. * @param tData `TData` associated with the current `LView`. * @param index location of where `tStyleValue` should be stored (and linked into list.) * @param isClassBinding True if the associated `tStylingKey` as a `class` styling. * `tNode.classBindings` should be used (or `tNode.styleBindings` otherwise.) */ function markDuplicateOfResidualStyling(tNode, tStylingKey, tData, index, isClassBinding) { const residual = isClassBinding ? tNode.residualClasses : tNode.residualStyles; if (residual != null /* or undefined */ && typeof tStylingKey == 'string' && keyValueArrayIndexOf(residual, tStylingKey) >= 0) { // We have duplicate in the residual so mark ourselves as duplicate. tData[index + 1] = setTStylingRangeNextDuplicate(tData[index + 1]); } } /** * Marks `TStyleValue`s as duplicates if another style binding in the list has the same * `TStyleValue`. * * NOTE: this function is intended to be called twice once with `isPrevDir` set to `true` and once * with it set to `false` to search both the previous as well as next items in the list. * * No duplicate case * ``` * [style.color] * [style.width.px] <<- index * [style.height.px] * ``` * * In the above case adding `[style.width.px]` to the existing `[style.color]` produces no * duplicates because `width` is not found in any other part of the linked list. * * Duplicate case * ``` * [style.color] * [style.width.em] * [style.width.px] <<- index * ``` * In the above case adding `[style.width.px]` will produce a duplicate with `[style.width.em]` * because `width` is found in the chain. * * Map case 1 * ``` * [style.width.px] * [style.color] * [style] <<- index * ``` * In the above case adding `[style]` will produce a duplicate with any other bindings because * `[style]` is a Map and as such is fully dynamic and could produce `color` or `width`. * * Map case 2 * ``` * [style] * [style.width.px] * [style.color] <<- index * ``` * In the above case adding `[style.color]` will produce a duplicate because there is already a * `[style]` binding which is a Map and as such is fully dynamic and could produce `color` or * `width`. * * NOTE: Once `[style]` (Map) is added into the system all things are mapped as duplicates. * NOTE: We use `style` as example, but same logic is applied to `class`es as well. * * @param tData `TData` where the linked list is stored. * @param tStylingKey `TStylingKeyPrimitive` which contains the value to compare to other keys in * the linked list. * @param index Starting location in the linked list to search from * @param isPrevDir Direction. * - `true` for previous (lower priority); * - `false` for next (higher priority). */ function markDuplicates(tData, tStylingKey, index, isPrevDir, isClassBinding) { const tStylingAtIndex = tData[index + 1]; const isMap = tStylingKey === null; let cursor = isPrevDir ? getTStylingRangePrev(tStylingAtIndex) : getTStylingRangeNext(tStylingAtIndex); let foundDuplicate = false; // We keep iterating as long as we have a cursor // AND either: // - we found what we are looking for, OR // - we are a map in which case we have to continue searching even after we find what we were // looking for since we are a wild card and everything needs to be flipped to duplicate. while (cursor !== 0 && (foundDuplicate === false || isMap)) { ngDevMode && assertIndexInRange(tData, cursor); const tStylingValueAtCursor = tData[cursor]; const tStyleRangeAtCursor = tData[cursor + 1]; if (isStylingMatch(tStylingValueAtCursor, tStylingKey)) { foundDuplicate = true; tData[cursor + 1] = isPrevDir ? setTStylingRangeNextDuplicate(tStyleRangeAtCursor) : setTStylingRangePrevDuplicate(tStyleRangeAtCursor); } cursor = isPrevDir ? getTStylingRangePrev(tStyleRangeAtCursor) : getTStylingRangeNext(tStyleRangeAtCursor); } if (foundDuplicate) { // if we found a duplicate, than mark ourselves. tData[index + 1] = isPrevDir ? setTStylingRangePrevDuplicate(tStylingAtIndex) : setTStylingRangeNextDuplicate(tStylingAtIndex); } } /** * Determines if two `TStylingKey`s are a match. * * When computing whether a binding contains a duplicate, we need to compare if the instruction * `TStylingKey` has a match. * * Here are examples of `TStylingKey`s which match given `tStylingKeyCursor` is: * - `color` * - `color` // Match another color * - `null` // That means that `tStylingKey` is a `classMap`/`styleMap` instruction * - `['', 'color', 'other', true]` // wrapped `color` so match * - `['', null, 'other', true]` // wrapped `null` so match * - `['', 'width', 'color', 'value']` // wrapped static value contains a match on `'color'` * - `null` // `tStylingKeyCursor` always match as it is `classMap`/`styleMap` instruction * * @param tStylingKeyCursor * @param tStylingKey */ function isStylingMatch(tStylingKeyCursor, tStylingKey) { ngDevMode && assertNotEqual(Array.isArray(tStylingKey), true, 'Expected that \'tStylingKey\' has been unwrapped'); if (tStylingKeyCursor === null || // If the cursor is `null` it means that we have map at that // location so we must assume that we have a match. tStylingKey == null || // If `tStylingKey` is `null` then it is a map therefor assume that it // contains a match. (Array.isArray(tStylingKeyCursor) ? tStylingKeyCursor[1] : tStylingKeyCursor) === tStylingKey // If the keys match explicitly than we are a match. ) { return true; } else if (Array.isArray(tStylingKeyCursor) && typeof tStylingKey === 'string') { // if we did not find a match, but `tStylingKeyCursor` is `KeyValueArray` that means cursor has // statics and we need to check those as well. return keyValueArrayIndexOf(tStylingKeyCursor, tStylingKey) >= 0; // see if we are matching the key } return false; } // Global state of the parser. (This makes parser non-reentrant, but that is not an issue) const parserState = { textEnd: 0, key: 0, keyEnd: 0, value: 0, valueEnd: 0, }; /** * Retrieves the last parsed `key` of style. * @param text the text to substring the key from. */ function getLastParsedKey(text) { return text.substring(parserState.key, parserState.keyEnd); } /** * Retrieves the last parsed `value` of style. * @param text the text to substring the key from. */ function getLastParsedValue(text) { return text.substring(parserState.value, parserState.valueEnd); } /** * Initializes `className` string for parsing and parses the first token. * * This function is intended to be used in this format: * ``` * for (let i = parseClassName(text); i >= 0; i = parseClassNameNext(text, i)) { * const key = getLastParsedKey(); * ... * } * ``` * @param text `className` to parse * @returns index where the next invocation of `parseClassNameNext` should resume. */ function parseClassName(text) { resetParserState(text); return parseClassNameNext(text, consumeWhitespace(text, 0, parserState.textEnd)); } /** * Parses next `className` token. * * This function is intended to be used in this format: * ``` * for (let i = parseClassName(text); i >= 0; i = parseClassNameNext(text, i)) { * const key = getLastParsedKey(); * ... * } * ``` * * @param text `className` to parse * @param index where the parsing should resume. * @returns index where the next invocation of `parseClassNameNext` should resume. */ function parseClassNameNext(text, index) { const end = parserState.textEnd; if (end === index) { return -1; } index = parserState.keyEnd = consumeClassToken(text, parserState.key = index, end); return consumeWhitespace(text, index, end); } /** * Initializes `cssText` string for parsing and parses the first key/values. * * This function is intended to be used in this format: * ``` * for (let i = parseStyle(text); i >= 0; i = parseStyleNext(text, i))) { * const key = getLastParsedKey(); * const value = getLastParsedValue(); * ... * } * ``` * @param text `cssText` to parse * @returns index where the next invocation of `parseStyleNext` should resume. */ function parseStyle(text) { resetParserState(text); return parseStyleNext(text, consumeWhitespace(text, 0, parserState.textEnd)); } /** * Parses the next `cssText` key/values. * * This function is intended to be used in this format: * ``` * for (let i = parseStyle(text); i >= 0; i = parseStyleNext(text, i))) { * const key = getLastParsedKey(); * const value = getLastParsedValue(); * ... * } * * @param text `cssText` to parse * @param index where the parsing should resume. * @returns index where the next invocation of `parseStyleNext` should resume. */ function parseStyleNext(text, startIndex) { const end = parserState.textEnd; let index = parserState.key = consumeWhitespace(text, startIndex, end); if (end === index) { // we reached an end so just quit return -1; } index = parserState.keyEnd = consumeStyleKey(text, index, end); index = consumeSeparator(text, index, end, 58 /* CharCode.COLON */); index = parserState.value = consumeWhitespace(text, index, end); index = parserState.valueEnd = consumeStyleValue(text, index, end); return consumeSeparator(text, index, end, 59 /* CharCode.SEMI_COLON */); } /** * Reset the global state of the styling parser. * @param text The styling text to parse. */ function resetParserState(text) { parserState.key = 0; parserState.keyEnd = 0; parserState.value = 0; parserState.valueEnd = 0; parserState.textEnd = text.length; } /** * Returns index of next non-whitespace character. * * @param text Text to scan * @param startIndex Starting index of character where the scan should start. * @param endIndex Ending index of character where the scan should end. * @returns Index of next non-whitespace character (May be the same as `start` if no whitespace at * that location.) */ function consumeWhitespace(text, startIndex, endIndex) { while (startIndex < endIndex && text.charCodeAt(startIndex) <= 32 /* CharCode.SPACE */) { startIndex++; } return startIndex; } /** * Returns index of last char in class token. * * @param text Text to scan * @param startIndex Starting index of character where the scan should start. * @param endIndex Ending index of character where the scan should end. * @returns Index after last char in class token. */ function consumeClassToken(text, startIndex, endIndex) { while (startIndex < endIndex && text.charCodeAt(startIndex) > 32 /* CharCode.SPACE */) { startIndex++; } return startIndex; } /** * Consumes all of the characters belonging to style key and token. * * @param text Text to scan * @param startIndex Starting index of character where the scan should start. * @param endIndex Ending index of character where the scan should end. * @returns Index after last style key character. */ function consumeStyleKey(text, startIndex, endIndex) { let ch; while (startIndex < endIndex && ((ch = text.charCodeAt(startIndex)) === 45 /* CharCode.DASH */ || ch === 95 /* CharCode.UNDERSCORE */ || ((ch & -33 /* CharCode.UPPER_CASE */) >= 65 /* CharCode.A */ && (ch & -33 /* CharCode.UPPER_CASE */) <= 90 /* CharCode.Z */) || (ch >= 48 /* CharCode.ZERO */ && ch <= 57 /* CharCode.NINE */))) { startIndex++; } return startIndex; } /** * Consumes all whitespace and the separator `:` after the style key. * * @param text Text to scan * @param startIndex Starting index of character where the scan should start. * @param endIndex Ending index of character where the scan should end. * @returns Index after separator and surrounding whitespace. */ function consumeSeparator(text, startIndex, endIndex, separator) { startIndex = consumeWhitespace(text, startIndex, endIndex); if (startIndex < endIndex) { if (ngDevMode && text.charCodeAt(startIndex) !== separator) { malformedStyleError(text, String.fromCharCode(separator), startIndex); } startIndex++; } return startIndex; } /** * Consumes style value honoring `url()` and `""` text. * * @param text Text to scan * @param startIndex Starting index of character where the scan should start. * @param endIndex Ending index of character where the scan should end. * @returns Index after last style value character. */ function consumeStyleValue(text, startIndex, endIndex) { let ch1 = -1; // 1st previous character let ch2 = -1; // 2nd previous character let ch3 = -1; // 3rd previous character let i = startIndex; let lastChIndex = i; while (i < endIndex) { const ch = text.charCodeAt(i++); if (ch === 59 /* CharCode.SEMI_COLON */) { return lastChIndex; } else if (ch === 34 /* CharCode.DOUBLE_QUOTE */ || ch === 39 /* CharCode.SINGLE_QUOTE */) { lastChIndex = i = consumeQuotedText(text, ch, i, endIndex); } else if (startIndex === i - 4 && // We have seen only 4 characters so far "URL(" (Ignore "foo_URL()") ch3 === 85 /* CharCode.U */ && ch2 === 82 /* CharCode.R */ && ch1 === 76 /* CharCode.L */ && ch === 40 /* CharCode.OPEN_PAREN */) { lastChIndex = i = consumeQuotedText(text, 41 /* CharCode.CLOSE_PAREN */, i, endIndex); } else if (ch > 32 /* CharCode.SPACE */) { // if we have a non-whitespace character then capture its location lastChIndex = i; } ch3 = ch2; ch2 = ch1; ch1 = ch & -33 /* CharCode.UPPER_CASE */; } return lastChIndex; } /** * Consumes all of the quoted characters. * * @param text Text to scan * @param quoteCharCode CharCode of either `"` or `'` quote or `)` for `url(...)`. * @param startIndex Starting index of character where the scan should start. * @param endIndex Ending index of character where the scan should end. * @returns Index after quoted characters. */ function consumeQuotedText(text, quoteCharCode, startIndex, endIndex) { let ch1 = -1; // 1st previous character let index = startIndex; while (index < endIndex) { const ch = text.charCodeAt(index++); if (ch == quoteCharCode && ch1 !== 92 /* CharCode.BACK_SLASH */) { return index; } if (ch == 92 /* CharCode.BACK_SLASH */ && ch1 === 92 /* CharCode.BACK_SLASH */) { // two back slashes cancel each other out. For example `"\\"` should properly end the // quotation. (It should not assume that the last `"` is escaped.) ch1 = 0; } else { ch1 = ch; } } throw ngDevMode ? malformedStyleError(text, String.fromCharCode(quoteCharCode), endIndex) : new Error(); } function malformedStyleError(text, expecting, index) { ngDevMode && assertEqual(typeof text === 'string', true, 'String expected here'); throw throwError(`Malformed style at location ${index} in string '` + text.substring(0, index) + '[>>' + text.substring(index, index + 1) + '<<]' + text.slice(index + 1) + `'. Expecting '${expecting}'.`); } /** * Update a style binding on an element with the provided value. * * If the style value is falsy then it will be removed from the element * (or assigned a different value depending if there are any styles placed * on the element with `styleMap` or any static styles that are * present from when the element was created with `styling`). * * Note that the styling element is updated as part of `stylingApply`. * * @param prop A valid CSS property. * @param value New value to write (`null` or an empty string to remove). * @param suffix Optional suffix. Used with scalar values to add unit such as `px`. * * Note that this will apply the provided style value to the host element if this function is called * within a host binding function. * * @codeGenApi */ function ɵɵstyleProp(prop, value, suffix) { checkStylingProperty(prop, value, suffix, false); return ɵɵstyleProp; } /** * Update a class binding on an element with the provided value. * * This instruction is meant to handle the `[class.foo]="exp"` case and, * therefore, the class binding itself must already be allocated using * `styling` within the creation block. * * @param prop A valid CSS class (only one). * @param value A true/false value which will turn the class on or off. * * Note that this will apply the provided class value to the host element if this function * is called within a host binding function. * * @codeGenApi */ function ɵɵclassProp(className, value) { checkStylingProperty(className, value, null, true); return ɵɵclassProp; } /** * Update style bindings using an object literal on an element. * * This instruction is meant to apply styling via the `[style]="exp"` template bindings. * When styles are applied to the element they will then be updated with respect to * any styles/classes set via `styleProp`. If any styles are set to falsy * then they will be removed from the element. * * Note that the styling instruction will not be applied until `stylingApply` is called. * * @param styles A key/value style map of the styles that will be applied to the given element. * Any missing styles (that have already been applied to the element beforehand) will be * removed (unset) from the element's styling. * * Note that this will apply the provided styleMap value to the host element if this function * is called within a host binding. * * @codeGenApi */ function ɵɵstyleMap(styles) { checkStylingMap(styleKeyValueArraySet, styleStringParser, styles, false); } /** * Parse text as style and add values to KeyValueArray. * * This code is pulled out to a separate function so that it can be tree shaken away if it is not * needed. It is only referenced from `ɵɵstyleMap`. * * @param keyValueArray KeyValueArray to add parsed values to. * @param text text to parse. */ function styleStringParser(keyValueArray, text) { for (let i = parseStyle(text); i >= 0; i = parseStyleNext(text, i)) { styleKeyValueArraySet(keyValueArray, getLastParsedKey(text), getLastParsedValue(text)); } } /** * Update class bindings using an object literal or class-string on an element. * * This instruction is meant to apply styling via the `[class]="exp"` template bindings. * When classes are applied to the element they will then be updated with * respect to any styles/classes set via `classProp`. If any * classes are set to falsy then they will be removed from the element. * * Note that the styling instruction will not be applied until `stylingApply` is called. * Note that this will the provided classMap value to the host element if this function is called * within a host binding. * * @param classes A key/value map or string of CSS classes that will be added to the * given element. Any missing classes (that have already been applied to the element * beforehand) will be removed (unset) from the element's list of CSS classes. * * @codeGenApi */ function ɵɵclassMap(classes) { checkStylingMap(classKeyValueArraySet, classStringParser, classes, true); } /** * Parse text as class and add values to KeyValueArray. * * This code is pulled out to a separate function so that it can be tree shaken away if it is not * needed. It is only referenced from `ɵɵclassMap`. * * @param keyValueArray KeyValueArray to add parsed values to. * @param text text to parse. */ function classStringParser(keyValueArray, text) { for (let i = parseClassName(text); i >= 0; i = parseClassNameNext(text, i)) { keyValueArraySet(keyValueArray, getLastParsedKey(text), true); } } /** * Common code between `ɵɵclassProp` and `ɵɵstyleProp`. * * @param prop property name. * @param value binding value. * @param suffix suffix for the property (e.g. `em` or `px`) * @param isClassBased `true` if `class` change (`false` if `style`) */ function checkStylingProperty(prop, value, suffix, isClassBased) { const lView = getLView(); const tView = getTView(); // Styling instructions use 2 slots per binding. // 1. one for the value / TStylingKey // 2. one for the intermittent-value / TStylingRange const bindingIndex = incrementBindingIndex(2); if (tView.firstUpdatePass) { stylingFirstUpdatePass(tView, prop, bindingIndex, isClassBased); } if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) { const tNode = tView.data[getSelectedIndex()]; updateStyling(tView, tNode, lView, lView[RENDERER], prop, lView[bindingIndex + 1] = normalizeSuffix(value, suffix), isClassBased, bindingIndex); } } /** * Common code between `ɵɵclassMap` and `ɵɵstyleMap`. * * @param keyValueArraySet (See `keyValueArraySet` in "util/array_utils") Gets passed in as a * function so that `style` can be processed. This is done for tree shaking purposes. * @param stringParser Parser used to parse `value` if `string`. (Passed in as `style` and `class` * have different parsers.) * @param value bound value from application * @param isClassBased `true` if `class` change (`false` if `style`) */ function checkStylingMap(keyValueArraySet, stringParser, value, isClassBased) { const tView = getTView(); const bindingIndex = incrementBindingIndex(2); if (tView.firstUpdatePass) { stylingFirstUpdatePass(tView, null, bindingIndex, isClassBased); } const lView = getLView(); if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) { // `getSelectedIndex()` should be here (rather than in instruction) so that it is guarded by the // if so as not to read unnecessarily. const tNode = tView.data[getSelectedIndex()]; if (hasStylingInputShadow(tNode, isClassBased) && !isInHostBindings(tView, bindingIndex)) { if (ngDevMode) { // verify that if we are shadowing then `TData` is appropriately marked so that we skip // processing this binding in styling resolution. const tStylingKey = tView.data[bindingIndex]; assertEqual(Array.isArray(tStylingKey) ? tStylingKey[1] : tStylingKey, false, 'Styling linked list shadow input should be marked as \'false\''); } // VE does not concatenate the static portion like we are doing here. // Instead VE just ignores the static completely if dynamic binding is present. // Because of locality we have already set the static portion because we don't know if there // is a dynamic portion until later. If we would ignore the static portion it would look like // the binding has removed it. This would confuse `[ngStyle]`/`[ngClass]` to do the wrong // thing as it would think that the static portion was removed. For this reason we // concatenate it so that `[ngStyle]`/`[ngClass]` can continue to work on changed. let staticPrefix = isClassBased ? tNode.classesWithoutHost : tNode.stylesWithoutHost; ngDevMode && isClassBased === false && staticPrefix !== null && assertEqual(staticPrefix.endsWith(';'), true, 'Expecting static portion to end with \';\''); if (staticPrefix !== null) { // We want to make sure that falsy values of `value` become empty strings. value = concatStringsWithSpace(staticPrefix, value ? value : ''); } // Given `
` such that `my-dir` has `@Input('style')`. // This takes over the `[style]` binding. (Same for `[class]`) setDirectiveInputsWhichShadowsStyling(tView, tNode, lView, value, isClassBased); } else { updateStylingMap(tView, tNode, lView, lView[RENDERER], lView[bindingIndex + 1], lView[bindingIndex + 1] = toStylingKeyValueArray(keyValueArraySet, stringParser, value), isClassBased, bindingIndex); } } } /** * Determines when the binding is in `hostBindings` section * * @param tView Current `TView` * @param bindingIndex index of binding which we would like if it is in `hostBindings` */ function isInHostBindings(tView, bindingIndex) { // All host bindings are placed after the expando section. return bindingIndex >= tView.expandoStartIndex; } /** * Collects the necessary information to insert the binding into a linked list of style bindings * using `insertTStylingBinding`. * * @param tView `TView` where the binding linked list will be stored. * @param tStylingKey Property/key of the binding. * @param bindingIndex Index of binding associated with the `prop` * @param isClassBased `true` if `class` change (`false` if `style`) */ function stylingFirstUpdatePass(tView, tStylingKey, bindingIndex, isClassBased) { ngDevMode && assertFirstUpdatePass(tView); const tData = tView.data; if (tData[bindingIndex + 1] === null) { // The above check is necessary because we don't clear first update pass until first successful // (no exception) template execution. This prevents the styling instruction from double adding // itself to the list. // `getSelectedIndex()` should be here (rather than in instruction) so that it is guarded by the // if so as not to read unnecessarily. const tNode = tData[getSelectedIndex()]; ngDevMode && assertDefined(tNode, 'TNode expected'); const isHostBindings = isInHostBindings(tView, bindingIndex); if (hasStylingInputShadow(tNode, isClassBased) && tStylingKey === null && !isHostBindings) { // `tStylingKey === null` implies that we are either `[style]` or `[class]` binding. // If there is a directive which uses `@Input('style')` or `@Input('class')` than // we need to neutralize this binding since that directive is shadowing it. // We turn this into a noop by setting the key to `false` tStylingKey = false; } tStylingKey = wrapInStaticStylingKey(tData, tNode, tStylingKey, isClassBased); insertTStylingBinding(tData, tNode, tStylingKey, bindingIndex, isHostBindings, isClassBased); } } /** * Adds static styling information to the binding if applicable. * * The linked list of styles not only stores the list and keys, but also stores static styling * information on some of the keys. This function determines if the key should contain the styling * information and computes it. * * See `TStylingStatic` for more details. * * @param tData `TData` where the linked list is stored. * @param tNode `TNode` for which the styling is being computed. * @param stylingKey `TStylingKeyPrimitive` which may need to be wrapped into `TStylingKey` * @param isClassBased `true` if `class` (`false` if `style`) */ function wrapInStaticStylingKey(tData, tNode, stylingKey, isClassBased) { const hostDirectiveDef = getCurrentDirectiveDef(tData); let residual = isClassBased ? tNode.residualClasses : tNode.residualStyles; if (hostDirectiveDef === null) { // We are in template node. // If template node already had styling instruction then it has already collected the static // styling and there is no need to collect them again. We know that we are the first styling // instruction because the `TNode.*Bindings` points to 0 (nothing has been inserted yet). const isFirstStylingInstructionInTemplate = (isClassBased ? tNode.classBindings : tNode.styleBindings) === 0; if (isFirstStylingInstructionInTemplate) { // It would be nice to be able to get the statics from `mergeAttrs`, however, at this point // they are already merged and it would not be possible to figure which property belongs where // in the priority. stylingKey = collectStylingFromDirectives(null, tData, tNode, stylingKey, isClassBased); stylingKey = collectStylingFromTAttrs(stylingKey, tNode.attrs, isClassBased); // We know that if we have styling binding in template we can't have residual. residual = null; } } else { // We are in host binding node and there was no binding instruction in template node. // This means that we need to compute the residual. const directiveStylingLast = tNode.directiveStylingLast; const isFirstStylingInstructionInHostBinding = directiveStylingLast === -1 || tData[directiveStylingLast] !== hostDirectiveDef; if (isFirstStylingInstructionInHostBinding) { stylingKey = collectStylingFromDirectives(hostDirectiveDef, tData, tNode, stylingKey, isClassBased); if (residual === null) { // - If `null` than either: // - Template styling instruction already ran and it has consumed the static // styling into its `TStylingKey` and so there is no need to update residual. Instead // we need to update the `TStylingKey` associated with the first template node // instruction. OR // - Some other styling instruction ran and determined that there are no residuals let templateStylingKey = getTemplateHeadTStylingKey(tData, tNode, isClassBased); if (templateStylingKey !== undefined && Array.isArray(templateStylingKey)) { // Only recompute if `templateStylingKey` had static values. (If no static value found // then there is nothing to do since this operation can only produce less static keys, not // more.) templateStylingKey = collectStylingFromDirectives(null, tData, tNode, templateStylingKey[1] /* unwrap previous statics */, isClassBased); templateStylingKey = collectStylingFromTAttrs(templateStylingKey, tNode.attrs, isClassBased); setTemplateHeadTStylingKey(tData, tNode, isClassBased, templateStylingKey); } } else { // We only need to recompute residual if it is not `null`. // - If existing residual (implies there was no template styling). This means that some of // the statics may have moved from the residual to the `stylingKey` and so we have to // recompute. // - If `undefined` this is the first time we are running. residual = collectResidual(tData, tNode, isClassBased); } } } if (residual !== undefined) { isClassBased ? (tNode.residualClasses = residual) : (tNode.residualStyles = residual); } return stylingKey; } /** * Retrieve the `TStylingKey` for the template styling instruction. * * This is needed since `hostBinding` styling instructions are inserted after the template * instruction. While the template instruction needs to update the residual in `TNode` the * `hostBinding` instructions need to update the `TStylingKey` of the template instruction because * the template instruction is downstream from the `hostBindings` instructions. * * @param tData `TData` where the linked list is stored. * @param tNode `TNode` for which the styling is being computed. * @param isClassBased `true` if `class` (`false` if `style`) * @return `TStylingKey` if found or `undefined` if not found. */ function getTemplateHeadTStylingKey(tData, tNode, isClassBased) { const bindings = isClassBased ? tNode.classBindings : tNode.styleBindings; if (getTStylingRangeNext(bindings) === 0) { // There does not seem to be a styling instruction in the `template`. return undefined; } return tData[getTStylingRangePrev(bindings)]; } /** * Update the `TStylingKey` of the first template instruction in `TNode`. * * Logically `hostBindings` styling instructions are of lower priority than that of the template. * However, they execute after the template styling instructions. This means that they get inserted * in front of the template styling instructions. * * If we have a template styling instruction and a new `hostBindings` styling instruction is * executed it means that it may need to steal static fields from the template instruction. This * method allows us to update the first template instruction `TStylingKey` with a new value. * * Assume: * ``` *
* * @Directive({ * host: { * 'style': 'width: 100px', * '[style.color]': 'dirExp', * } * }) * class MyDir {} * ``` * * when `[style.color]="tmplExp"` executes it creates this data structure. * ``` * ['', 'color', 'color', 'red', 'width', '100px'], * ``` * * The reason for this is that the template instruction does not know if there are styling * instructions and must assume that there are none and must collect all of the static styling. * (both * `color' and 'width`) * * When `'[style.color]': 'dirExp',` executes we need to insert a new data into the linked list. * ``` * ['', 'color', 'width', '100px'], // newly inserted * ['', 'color', 'color', 'red', 'width', '100px'], // this is wrong * ``` * * Notice that the template statics is now wrong as it incorrectly contains `width` so we need to * update it like so: * ``` * ['', 'color', 'width', '100px'], * ['', 'color', 'color', 'red'], // UPDATE * ``` * * @param tData `TData` where the linked list is stored. * @param tNode `TNode` for which the styling is being computed. * @param isClassBased `true` if `class` (`false` if `style`) * @param tStylingKey New `TStylingKey` which is replacing the old one. */ function setTemplateHeadTStylingKey(tData, tNode, isClassBased, tStylingKey) { const bindings = isClassBased ? tNode.classBindings : tNode.styleBindings; ngDevMode && assertNotEqual(getTStylingRangeNext(bindings), 0, 'Expecting to have at least one template styling binding.'); tData[getTStylingRangePrev(bindings)] = tStylingKey; } /** * Collect all static values after the current `TNode.directiveStylingLast` index. * * Collect the remaining styling information which has not yet been collected by an existing * styling instruction. * * @param tData `TData` where the `DirectiveDefs` are stored. * @param tNode `TNode` which contains the directive range. * @param isClassBased `true` if `class` (`false` if `style`) */ function collectResidual(tData, tNode, isClassBased) { let residual = undefined; const directiveEnd = tNode.directiveEnd; ngDevMode && assertNotEqual(tNode.directiveStylingLast, -1, 'By the time this function gets called at least one hostBindings-node styling instruction must have executed.'); // We add `1 + tNode.directiveStart` because we need to skip the current directive (as we are // collecting things after the last `hostBindings` directive which had a styling instruction.) for (let i = 1 + tNode.directiveStylingLast; i < directiveEnd; i++) { const attrs = tData[i].hostAttrs; residual = collectStylingFromTAttrs(residual, attrs, isClassBased); } return collectStylingFromTAttrs(residual, tNode.attrs, isClassBased); } /** * Collect the static styling information with lower priority than `hostDirectiveDef`. * * (This is opposite of residual styling.) * * @param hostDirectiveDef `DirectiveDef` for which we want to collect lower priority static * styling. (Or `null` if template styling) * @param tData `TData` where the linked list is stored. * @param tNode `TNode` for which the styling is being computed. * @param stylingKey Existing `TStylingKey` to update or wrap. * @param isClassBased `true` if `class` (`false` if `style`) */ function collectStylingFromDirectives(hostDirectiveDef, tData, tNode, stylingKey, isClassBased) { // We need to loop because there can be directives which have `hostAttrs` but don't have // `hostBindings` so this loop catches up to the current directive.. let currentDirective = null; const directiveEnd = tNode.directiveEnd; let directiveStylingLast = tNode.directiveStylingLast; if (directiveStylingLast === -1) { directiveStylingLast = tNode.directiveStart; } else { directiveStylingLast++; } while (directiveStylingLast < directiveEnd) { currentDirective = tData[directiveStylingLast]; ngDevMode && assertDefined(currentDirective, 'expected to be defined'); stylingKey = collectStylingFromTAttrs(stylingKey, currentDirective.hostAttrs, isClassBased); if (currentDirective === hostDirectiveDef) break; directiveStylingLast++; } if (hostDirectiveDef !== null) { // we only advance the styling cursor if we are collecting data from host bindings. // Template executes before host bindings and so if we would update the index, // host bindings would not get their statics. tNode.directiveStylingLast = directiveStylingLast; } return stylingKey; } /** * Convert `TAttrs` into `TStylingStatic`. * * @param stylingKey existing `TStylingKey` to update or wrap. * @param attrs `TAttributes` to process. * @param isClassBased `true` if `class` (`false` if `style`) */ function collectStylingFromTAttrs(stylingKey, attrs, isClassBased) { const desiredMarker = isClassBased ? 1 /* AttributeMarker.Classes */ : 2 /* AttributeMarker.Styles */; let currentMarker = -1 /* AttributeMarker.ImplicitAttributes */; if (attrs !== null) { for (let i = 0; i < attrs.length; i++) { const item = attrs[i]; if (typeof item === 'number') { currentMarker = item; } else { if (currentMarker === desiredMarker) { if (!Array.isArray(stylingKey)) { stylingKey = stylingKey === undefined ? [] : ['', stylingKey]; } keyValueArraySet(stylingKey, item, isClassBased ? true : attrs[++i]); } } } } return stylingKey === undefined ? null : stylingKey; } /** * Convert user input to `KeyValueArray`. * * This function takes user input which could be `string`, Object literal, or iterable and converts * it into a consistent representation. The output of this is `KeyValueArray` (which is an array * where * even indexes contain keys and odd indexes contain values for those keys). * * The advantage of converting to `KeyValueArray` is that we can perform diff in an input * independent * way. * (ie we can compare `foo bar` to `['bar', 'baz'] and determine a set of changes which need to be * applied) * * The fact that `KeyValueArray` is sorted is very important because it allows us to compute the * difference in linear fashion without the need to allocate any additional data. * * For example if we kept this as a `Map` we would have to iterate over previous `Map` to determine * which values need to be deleted, over the new `Map` to determine additions, and we would have to * keep additional `Map` to keep track of duplicates or items which have not yet been visited. * * @param keyValueArraySet (See `keyValueArraySet` in "util/array_utils") Gets passed in as a * function so that `style` can be processed. This is done * for tree shaking purposes. * @param stringParser The parser is passed in so that it will be tree shakable. See * `styleStringParser` and `classStringParser` * @param value The value to parse/convert to `KeyValueArray` */ function toStylingKeyValueArray(keyValueArraySet, stringParser, value) { if (value == null /*|| value === undefined */ || value === '') return EMPTY_ARRAY; const styleKeyValueArray = []; const unwrappedValue = unwrapSafeValue(value); if (Array.isArray(unwrappedValue)) { for (let i = 0; i < unwrappedValue.length; i++) { keyValueArraySet(styleKeyValueArray, unwrappedValue[i], true); } } else if (typeof unwrappedValue === 'object') { for (const key in unwrappedValue) { if (unwrappedValue.hasOwnProperty(key)) { keyValueArraySet(styleKeyValueArray, key, unwrappedValue[key]); } } } else if (typeof unwrappedValue === 'string') { stringParser(styleKeyValueArray, unwrappedValue); } else { ngDevMode && throwError('Unsupported styling type ' + typeof unwrappedValue + ': ' + unwrappedValue); } return styleKeyValueArray; } /** * Set a `value` for a `key`. * * See: `keyValueArraySet` for details * * @param keyValueArray KeyValueArray to add to. * @param key Style key to add. * @param value The value to set. */ function styleKeyValueArraySet(keyValueArray, key, value) { keyValueArraySet(keyValueArray, key, unwrapSafeValue(value)); } /** * Class-binding-specific function for setting the `value` for a `key`. * * See: `keyValueArraySet` for details * * @param keyValueArray KeyValueArray to add to. * @param key Style key to add. * @param value The value to set. */ function classKeyValueArraySet(keyValueArray, key, value) { // We use `classList.add` to eventually add the CSS classes to the DOM node. Any value passed into // `add` is stringified and added to the `class` attribute, e.g. even null, undefined or numbers // will be added. Stringify the key here so that our internal data structure matches the value in // the DOM. The only exceptions are empty strings and strings that contain spaces for which // the browser throws an error. We ignore such values, because the error is somewhat cryptic. const stringKey = String(key); if (stringKey !== '' && !stringKey.includes(' ')) { keyValueArraySet(keyValueArray, stringKey, value); } } /** * Update map based styling. * * Map based styling could be anything which contains more than one binding. For example `string`, * or object literal. Dealing with all of these types would complicate the logic so * instead this function expects that the complex input is first converted into normalized * `KeyValueArray`. The advantage of normalization is that we get the values sorted, which makes it * very cheap to compute deltas between the previous and current value. * * @param tView Associated `TView.data` contains the linked list of binding priorities. * @param tNode `TNode` where the binding is located. * @param lView `LView` contains the values associated with other styling binding at this `TNode`. * @param renderer Renderer to use if any updates. * @param oldKeyValueArray Previous value represented as `KeyValueArray` * @param newKeyValueArray Current value represented as `KeyValueArray` * @param isClassBased `true` if `class` (`false` if `style`) * @param bindingIndex Binding index of the binding. */ function updateStylingMap(tView, tNode, lView, renderer, oldKeyValueArray, newKeyValueArray, isClassBased, bindingIndex) { if (oldKeyValueArray === NO_CHANGE) { // On first execution the oldKeyValueArray is NO_CHANGE => treat it as empty KeyValueArray. oldKeyValueArray = EMPTY_ARRAY; } let oldIndex = 0; let newIndex = 0; let oldKey = 0 < oldKeyValueArray.length ? oldKeyValueArray[0] : null; let newKey = 0 < newKeyValueArray.length ? newKeyValueArray[0] : null; while (oldKey !== null || newKey !== null) { ngDevMode && assertLessThan(oldIndex, 999, 'Are we stuck in infinite loop?'); ngDevMode && assertLessThan(newIndex, 999, 'Are we stuck in infinite loop?'); const oldValue = oldIndex < oldKeyValueArray.length ? oldKeyValueArray[oldIndex + 1] : undefined; const newValue = newIndex < newKeyValueArray.length ? newKeyValueArray[newIndex + 1] : undefined; let setKey = null; let setValue = undefined; if (oldKey === newKey) { // UPDATE: Keys are equal => new value is overwriting old value. oldIndex += 2; newIndex += 2; if (oldValue !== newValue) { setKey = newKey; setValue = newValue; } } else if (newKey === null || oldKey !== null && oldKey < newKey) { // DELETE: oldKey key is missing or we did not find the oldKey in the newValue // (because the keyValueArray is sorted and `newKey` is found later alphabetically). // `"background" < "color"` so we need to delete `"background"` because it is not found in the // new array. oldIndex += 2; setKey = oldKey; } else { // CREATE: newKey's is earlier alphabetically than oldKey's (or no oldKey) => we have new key. // `"color" > "background"` so we need to add `color` because it is in new array but not in // old array. ngDevMode && assertDefined(newKey, 'Expecting to have a valid key'); newIndex += 2; setKey = newKey; setValue = newValue; } if (setKey !== null) { updateStyling(tView, tNode, lView, renderer, setKey, setValue, isClassBased, bindingIndex); } oldKey = oldIndex < oldKeyValueArray.length ? oldKeyValueArray[oldIndex] : null; newKey = newIndex < newKeyValueArray.length ? newKeyValueArray[newIndex] : null; } } /** * Update a simple (property name) styling. * * This function takes `prop` and updates the DOM to that value. The function takes the binding * value as well as binding priority into consideration to determine which value should be written * to DOM. (For example it may be determined that there is a higher priority overwrite which blocks * the DOM write, or if the value goes to `undefined` a lower priority overwrite may be consulted.) * * @param tView Associated `TView.data` contains the linked list of binding priorities. * @param tNode `TNode` where the binding is located. * @param lView `LView` contains the values associated with other styling binding at this `TNode`. * @param renderer Renderer to use if any updates. * @param prop Either style property name or a class name. * @param value Either style value for `prop` or `true`/`false` if `prop` is class. * @param isClassBased `true` if `class` (`false` if `style`) * @param bindingIndex Binding index of the binding. */ function updateStyling(tView, tNode, lView, renderer, prop, value, isClassBased, bindingIndex) { if (!(tNode.type & 3 /* TNodeType.AnyRNode */)) { // It is possible to have styling on non-elements (such as ng-container). // This is rare, but it does happen. In such a case, just ignore the binding. return; } const tData = tView.data; const tRange = tData[bindingIndex + 1]; const higherPriorityValue = getTStylingRangeNextDuplicate(tRange) ? findStylingValue(tData, tNode, lView, prop, getTStylingRangeNext(tRange), isClassBased) : undefined; if (!isStylingValuePresent(higherPriorityValue)) { // We don't have a next duplicate, or we did not find a duplicate value. if (!isStylingValuePresent(value)) { // We should delete current value or restore to lower priority value. if (getTStylingRangePrevDuplicate(tRange)) { // We have a possible prev duplicate, let's retrieve it. value = findStylingValue(tData, null, lView, prop, bindingIndex, isClassBased); } } const rNode = getNativeByIndex(getSelectedIndex(), lView); applyStyling(renderer, isClassBased, rNode, prop, value); } } /** * Search for styling value with higher priority which is overwriting current value, or a * value of lower priority to which we should fall back if the value is `undefined`. * * When value is being applied at a location, related values need to be consulted. * - If there is a higher priority binding, we should be using that one instead. * For example `
` change to `exp1` * requires that we check `exp2` to see if it is set to value other than `undefined`. * - If there is a lower priority binding and we are changing to `undefined` * For example `
` change to `exp2` to * `undefined` requires that we check `exp1` (and static values) and use that as new value. * * NOTE: The styling stores two values. * 1. The raw value which came from the application is stored at `index + 0` location. (This value * is used for dirty checking). * 2. The normalized value is stored at `index + 1`. * * @param tData `TData` used for traversing the priority. * @param tNode `TNode` to use for resolving static styling. Also controls search direction. * - `TNode` search next and quit as soon as `isStylingValuePresent(value)` is true. * If no value found consult `tNode.residualStyle`/`tNode.residualClass` for default value. * - `null` search prev and go all the way to end. Return last value where * `isStylingValuePresent(value)` is true. * @param lView `LView` used for retrieving the actual values. * @param prop Property which we are interested in. * @param index Starting index in the linked list of styling bindings where the search should start. * @param isClassBased `true` if `class` (`false` if `style`) */ function findStylingValue(tData, tNode, lView, prop, index, isClassBased) { // `TNode` to use for resolving static styling. Also controls search direction. // - `TNode` search next and quit as soon as `isStylingValuePresent(value)` is true. // If no value found consult `tNode.residualStyle`/`tNode.residualClass` for default value. // - `null` search prev and go all the way to end. Return last value where // `isStylingValuePresent(value)` is true. const isPrevDirection = tNode === null; let value = undefined; while (index > 0) { const rawKey = tData[index]; const containsStatics = Array.isArray(rawKey); // Unwrap the key if we contain static values. const key = containsStatics ? rawKey[1] : rawKey; const isStylingMap = key === null; let valueAtLViewIndex = lView[index + 1]; if (valueAtLViewIndex === NO_CHANGE) { // In firstUpdatePass the styling instructions create a linked list of styling. // On subsequent passes it is possible for a styling instruction to try to read a binding // which // has not yet executed. In that case we will find `NO_CHANGE` and we should assume that // we have `undefined` (or empty array in case of styling-map instruction) instead. This // allows the resolution to apply the value (which may later be overwritten when the // binding actually executes.) valueAtLViewIndex = isStylingMap ? EMPTY_ARRAY : undefined; } let currentValue = isStylingMap ? keyValueArrayGet(valueAtLViewIndex, prop) : (key === prop ? valueAtLViewIndex : undefined); if (containsStatics && !isStylingValuePresent(currentValue)) { currentValue = keyValueArrayGet(rawKey, prop); } if (isStylingValuePresent(currentValue)) { value = currentValue; if (isPrevDirection) { return value; } } const tRange = tData[index + 1]; index = isPrevDirection ? getTStylingRangePrev(tRange) : getTStylingRangeNext(tRange); } if (tNode !== null) { // in case where we are going in next direction AND we did not find anything, we need to // consult residual styling let residual = isClassBased ? tNode.residualClasses : tNode.residualStyles; if (residual != null /** OR residual !=== undefined */) { value = keyValueArrayGet(residual, prop); } } return value; } /** * Determines if the binding value should be used (or if the value is 'undefined' and hence priority * resolution should be used.) * * @param value Binding style value. */ function isStylingValuePresent(value) { // Currently only `undefined` value is considered non-binding. That is `undefined` says I don't // have an opinion as to what this binding should be and you should consult other bindings by // priority to determine the valid value. // This is extracted into a single function so that we have a single place to control this. return value !== undefined; } /** * Normalizes and/or adds a suffix to the value. * * If value is `null`/`undefined` no suffix is added * @param value * @param suffix */ function normalizeSuffix(value, suffix) { if (value == null || value === '') ; else if (typeof suffix === 'string') { value = value + suffix; } else if (typeof value === 'object') { value = stringify(unwrapSafeValue(value)); } return value; } /** * Tests if the `TNode` has input shadow. * * An input shadow is when a directive steals (shadows) the input by using `@Input('style')` or * `@Input('class')` as input. * * @param tNode `TNode` which we would like to see if it has shadow. * @param isClassBased `true` if `class` (`false` if `style`) */ function hasStylingInputShadow(tNode, isClassBased) { return (tNode.flags & (isClassBased ? 8 /* TNodeFlags.hasClassInput */ : 16 /* TNodeFlags.hasStyleInput */)) !== 0; } /** * Create static text node * * @param index Index of the node in the data array * @param value Static string value to write. * * @codeGenApi */ function ɵɵtext(index, value = '') { const lView = getLView(); const tView = getTView(); const adjustedIndex = index + HEADER_OFFSET; ngDevMode && assertEqual(getBindingIndex(), tView.bindingStartIndex, 'text nodes should be created before any bindings'); ngDevMode && assertIndexInRange(lView, adjustedIndex); const tNode = tView.firstCreatePass ? getOrCreateTNode(tView, adjustedIndex, 1 /* TNodeType.Text */, value, null) : tView.data[adjustedIndex]; const textNative = _locateOrCreateTextNode(tView, lView, tNode, value, index); lView[adjustedIndex] = textNative; if (wasLastNodeCreated()) { appendChild(tView, lView, textNative, tNode); } // Text nodes are self closing. setCurrentTNode(tNode, false); } let _locateOrCreateTextNode = (tView, lView, tNode, value, index) => { lastNodeWasCreated(true); return createTextNode(lView[RENDERER], value); }; /** * Enables hydration code path (to lookup existing elements in DOM) * in addition to the regular creation mode of text nodes. */ function locateOrCreateTextNodeImpl(tView, lView, tNode, value, index) { const hydrationInfo = lView[HYDRATION]; const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode$1(hydrationInfo, index); lastNodeWasCreated(isNodeCreationMode); // Regular creation mode. if (isNodeCreationMode) { return createTextNode(lView[RENDERER], value); } // Hydration mode, looking up an existing element in DOM. const textNative = locateNextRNode(hydrationInfo, tView, lView, tNode); ngDevMode && validateMatchingNode(textNative, Node.TEXT_NODE, null, lView, tNode); ngDevMode && markRNodeAsClaimedByHydration(textNative); return textNative; } function enableLocateOrCreateTextNodeImpl() { _locateOrCreateTextNode = locateOrCreateTextNodeImpl; } /** * * Update text content with a lone bound value * * Used when a text node has 1 interpolated value in it, an no additional text * surrounds that interpolated value: * * ```html *
{{v0}}
* ``` * * Its compiled representation is: * * ```ts * ɵɵtextInterpolate(v0); * ``` * @returns itself, so that it may be chained. * @see textInterpolateV * @codeGenApi */ function ɵɵtextInterpolate(v0) { ɵɵtextInterpolate1('', v0, ''); return ɵɵtextInterpolate; } /** * * Update text content with single bound value surrounded by other text. * * Used when a text node has 1 interpolated value in it: * * ```html *
prefix{{v0}}suffix
* ``` * * Its compiled representation is: * * ```ts * ɵɵtextInterpolate1('prefix', v0, 'suffix'); * ``` * @returns itself, so that it may be chained. * @see textInterpolateV * @codeGenApi */ function ɵɵtextInterpolate1(prefix, v0, suffix) { const lView = getLView(); const interpolated = interpolation1(lView, prefix, v0, suffix); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated); } return ɵɵtextInterpolate1; } /** * * Update text content with 2 bound values surrounded by other text. * * Used when a text node has 2 interpolated values in it: * * ```html *
prefix{{v0}}-{{v1}}suffix
* ``` * * Its compiled representation is: * * ```ts * ɵɵtextInterpolate2('prefix', v0, '-', v1, 'suffix'); * ``` * @returns itself, so that it may be chained. * @see textInterpolateV * @codeGenApi */ function ɵɵtextInterpolate2(prefix, v0, i0, v1, suffix) { const lView = getLView(); const interpolated = interpolation2(lView, prefix, v0, i0, v1, suffix); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated); } return ɵɵtextInterpolate2; } /** * * Update text content with 3 bound values surrounded by other text. * * Used when a text node has 3 interpolated values in it: * * ```html *
prefix{{v0}}-{{v1}}-{{v2}}suffix
* ``` * * Its compiled representation is: * * ```ts * ɵɵtextInterpolate3( * 'prefix', v0, '-', v1, '-', v2, 'suffix'); * ``` * @returns itself, so that it may be chained. * @see textInterpolateV * @codeGenApi */ function ɵɵtextInterpolate3(prefix, v0, i0, v1, i1, v2, suffix) { const lView = getLView(); const interpolated = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated); } return ɵɵtextInterpolate3; } /** * * Update text content with 4 bound values surrounded by other text. * * Used when a text node has 4 interpolated values in it: * * ```html *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}suffix
* ``` * * Its compiled representation is: * * ```ts * ɵɵtextInterpolate4( * 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix'); * ``` * @returns itself, so that it may be chained. * @see ɵɵtextInterpolateV * @codeGenApi */ function ɵɵtextInterpolate4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix) { const lView = getLView(); const interpolated = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated); } return ɵɵtextInterpolate4; } /** * * Update text content with 5 bound values surrounded by other text. * * Used when a text node has 5 interpolated values in it: * * ```html *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}suffix
* ``` * * Its compiled representation is: * * ```ts * ɵɵtextInterpolate5( * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix'); * ``` * @returns itself, so that it may be chained. * @see textInterpolateV * @codeGenApi */ function ɵɵtextInterpolate5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix) { const lView = getLView(); const interpolated = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated); } return ɵɵtextInterpolate5; } /** * * Update text content with 6 bound values surrounded by other text. * * Used when a text node has 6 interpolated values in it: * * ```html *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}suffix
* ``` * * Its compiled representation is: * * ```ts * ɵɵtextInterpolate6( * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix'); * ``` * * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. @returns itself, so that it may be chained. * @see textInterpolateV * @codeGenApi */ function ɵɵtextInterpolate6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix) { const lView = getLView(); const interpolated = interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated); } return ɵɵtextInterpolate6; } /** * * Update text content with 7 bound values surrounded by other text. * * Used when a text node has 7 interpolated values in it: * * ```html *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}suffix
* ``` * * Its compiled representation is: * * ```ts * ɵɵtextInterpolate7( * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix'); * ``` * @returns itself, so that it may be chained. * @see textInterpolateV * @codeGenApi */ function ɵɵtextInterpolate7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix) { const lView = getLView(); const interpolated = interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated); } return ɵɵtextInterpolate7; } /** * * Update text content with 8 bound values surrounded by other text. * * Used when a text node has 8 interpolated values in it: * * ```html *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}suffix
* ``` * * Its compiled representation is: * * ```ts * ɵɵtextInterpolate8( * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, 'suffix'); * ``` * @returns itself, so that it may be chained. * @see textInterpolateV * @codeGenApi */ function ɵɵtextInterpolate8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix) { const lView = getLView(); const interpolated = interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated); } return ɵɵtextInterpolate8; } /** * Update text content with 9 or more bound values other surrounded by text. * * Used when the number of interpolated values exceeds 8. * * ```html *
prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}-{{v8}}-{{v9}}suffix
* ``` * * Its compiled representation is: * * ```ts * ɵɵtextInterpolateV( * ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9, * 'suffix']); * ``` *. * @param values The collection of values and the strings in between those values, beginning with * a string prefix and ending with a string suffix. * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`) * * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵtextInterpolateV(values) { const lView = getLView(); const interpolated = interpolationV(lView, values); if (interpolated !== NO_CHANGE) { textBindingInternal(lView, getSelectedIndex(), interpolated); } return ɵɵtextInterpolateV; } /** * * Update an interpolated class on an element with single bound value surrounded by text. * * Used when the value passed to a property has 1 interpolated value in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵclassMapInterpolate1('prefix', v0, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵclassMapInterpolate1(prefix, v0, suffix) { const lView = getLView(); const interpolatedValue = interpolation1(lView, prefix, v0, suffix); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } /** * * Update an interpolated class on an element with 2 bound values surrounded by text. * * Used when the value passed to a property has 2 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵclassMapInterpolate2('prefix', v0, '-', v1, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵclassMapInterpolate2(prefix, v0, i0, v1, suffix) { const lView = getLView(); const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } /** * * Update an interpolated class on an element with 3 bound values surrounded by text. * * Used when the value passed to a property has 3 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵclassMapInterpolate3( * 'prefix', v0, '-', v1, '-', v2, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵclassMapInterpolate3(prefix, v0, i0, v1, i1, v2, suffix) { const lView = getLView(); const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } /** * * Update an interpolated class on an element with 4 bound values surrounded by text. * * Used when the value passed to a property has 4 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵclassMapInterpolate4( * 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵclassMapInterpolate4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix) { const lView = getLView(); const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } /** * * Update an interpolated class on an element with 5 bound values surrounded by text. * * Used when the value passed to a property has 5 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵclassMapInterpolate5( * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵclassMapInterpolate5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix) { const lView = getLView(); const interpolatedValue = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } /** * * Update an interpolated class on an element with 6 bound values surrounded by text. * * Used when the value passed to a property has 6 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵclassMapInterpolate6( * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵclassMapInterpolate6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix) { const lView = getLView(); const interpolatedValue = interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } /** * * Update an interpolated class on an element with 7 bound values surrounded by text. * * Used when the value passed to a property has 7 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵclassMapInterpolate7( * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param i5 Static value used for concatenation only. * @param v6 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵclassMapInterpolate7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix) { const lView = getLView(); const interpolatedValue = interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } /** * * Update an interpolated class on an element with 8 bound values surrounded by text. * * Used when the value passed to a property has 8 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵclassMapInterpolate8( * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param i5 Static value used for concatenation only. * @param v6 Value checked for change. * @param i6 Static value used for concatenation only. * @param v7 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵclassMapInterpolate8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix) { const lView = getLView(); const interpolatedValue = interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } /** * Update an interpolated class on an element with 9 or more bound values surrounded by text. * * Used when the number of interpolated values exceeds 8. * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵclassMapInterpolateV( * ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9, * 'suffix']); * ``` *. * @param values The collection of values and the strings in-between those values, beginning with * a string prefix and ending with a string suffix. * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`) * @codeGenApi */ function ɵɵclassMapInterpolateV(values) { const lView = getLView(); const interpolatedValue = interpolationV(lView, values); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true); } /** * * Update an interpolated style on an element with single bound value surrounded by text. * * Used when the value passed to a property has 1 interpolated value in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstyleMapInterpolate1('key: ', v0, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵstyleMapInterpolate1(prefix, v0, suffix) { const lView = getLView(); const interpolatedValue = interpolation1(lView, prefix, v0, suffix); ɵɵstyleMap(interpolatedValue); } /** * * Update an interpolated style on an element with 2 bound values surrounded by text. * * Used when the value passed to a property has 2 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstyleMapInterpolate2('key: ', v0, '; key1: ', v1, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵstyleMapInterpolate2(prefix, v0, i0, v1, suffix) { const lView = getLView(); const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); ɵɵstyleMap(interpolatedValue); } /** * * Update an interpolated style on an element with 3 bound values surrounded by text. * * Used when the value passed to a property has 3 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstyleMapInterpolate3( * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵstyleMapInterpolate3(prefix, v0, i0, v1, i1, v2, suffix) { const lView = getLView(); const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); ɵɵstyleMap(interpolatedValue); } /** * * Update an interpolated style on an element with 4 bound values surrounded by text. * * Used when the value passed to a property has 4 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstyleMapInterpolate4( * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵstyleMapInterpolate4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix) { const lView = getLView(); const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); ɵɵstyleMap(interpolatedValue); } /** * * Update an interpolated style on an element with 5 bound values surrounded by text. * * Used when the value passed to a property has 5 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstyleMapInterpolate5( * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵstyleMapInterpolate5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix) { const lView = getLView(); const interpolatedValue = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); ɵɵstyleMap(interpolatedValue); } /** * * Update an interpolated style on an element with 6 bound values surrounded by text. * * Used when the value passed to a property has 6 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstyleMapInterpolate6( * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', v5, * 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵstyleMapInterpolate6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix) { const lView = getLView(); const interpolatedValue = interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); ɵɵstyleMap(interpolatedValue); } /** * * Update an interpolated style on an element with 7 bound values surrounded by text. * * Used when the value passed to a property has 7 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstyleMapInterpolate7( * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', v5, * '; key6: ', v6, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param i5 Static value used for concatenation only. * @param v6 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵstyleMapInterpolate7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix) { const lView = getLView(); const interpolatedValue = interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); ɵɵstyleMap(interpolatedValue); } /** * * Update an interpolated style on an element with 8 bound values surrounded by text. * * Used when the value passed to a property has 8 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstyleMapInterpolate8( * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', v5, * '; key6: ', v6, '; key7: ', v7, 'suffix'); * ``` * * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param i5 Static value used for concatenation only. * @param v6 Value checked for change. * @param i6 Static value used for concatenation only. * @param v7 Value checked for change. * @param suffix Static value used for concatenation only. * @codeGenApi */ function ɵɵstyleMapInterpolate8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix) { const lView = getLView(); const interpolatedValue = interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); ɵɵstyleMap(interpolatedValue); } /** * Update an interpolated style on an element with 9 or more bound values surrounded by text. * * Used when the number of interpolated values exceeds 8. * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstyleMapInterpolateV( * ['key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', v5, * '; key6: ', v6, '; key7: ', v7, '; key8: ', v8, '; key9: ', v9, 'suffix']); * ``` *. * @param values The collection of values and the strings in-between those values, beginning with * a string prefix and ending with a string suffix. * (e.g. `['prefix', value0, '; key2: ', value1, '; key2: ', value2, ..., value99, 'suffix']`) * @codeGenApi */ function ɵɵstyleMapInterpolateV(values) { const lView = getLView(); const interpolatedValue = interpolationV(lView, values); ɵɵstyleMap(interpolatedValue); } /** * * Update an interpolated style property on an element with single bound value surrounded by text. * * Used when the value passed to a property has 1 interpolated value in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstylePropInterpolate1(0, 'prefix', v0, 'suffix'); * ``` * * @param styleIndex Index of style to update. This index value refers to the * index of the style in the style bindings array that was passed into * `styling`. * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param suffix Static value used for concatenation only. * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵstylePropInterpolate1(prop, prefix, v0, suffix, valueSuffix) { const lView = getLView(); const interpolatedValue = interpolation1(lView, prefix, v0, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate1; } /** * * Update an interpolated style property on an element with 2 bound values surrounded by text. * * Used when the value passed to a property has 2 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstylePropInterpolate2(0, 'prefix', v0, '-', v1, 'suffix'); * ``` * * @param styleIndex Index of style to update. This index value refers to the * index of the style in the style bindings array that was passed into * `styling`. * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param suffix Static value used for concatenation only. * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵstylePropInterpolate2(prop, prefix, v0, i0, v1, suffix, valueSuffix) { const lView = getLView(); const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate2; } /** * * Update an interpolated style property on an element with 3 bound values surrounded by text. * * Used when the value passed to a property has 3 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstylePropInterpolate3(0, 'prefix', v0, '-', v1, '-', v2, 'suffix'); * ``` * * @param styleIndex Index of style to update. This index value refers to the * index of the style in the style bindings array that was passed into * `styling`. * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param suffix Static value used for concatenation only. * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵstylePropInterpolate3(prop, prefix, v0, i0, v1, i1, v2, suffix, valueSuffix) { const lView = getLView(); const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate3; } /** * * Update an interpolated style property on an element with 4 bound values surrounded by text. * * Used when the value passed to a property has 4 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstylePropInterpolate4(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix'); * ``` * * @param styleIndex Index of style to update. This index value refers to the * index of the style in the style bindings array that was passed into * `styling`. * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param suffix Static value used for concatenation only. * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵstylePropInterpolate4(prop, prefix, v0, i0, v1, i1, v2, i2, v3, suffix, valueSuffix) { const lView = getLView(); const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate4; } /** * * Update an interpolated style property on an element with 5 bound values surrounded by text. * * Used when the value passed to a property has 5 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstylePropInterpolate5(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix'); * ``` * * @param styleIndex Index of style to update. This index value refers to the * index of the style in the style bindings array that was passed into * `styling`. * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param suffix Static value used for concatenation only. * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵstylePropInterpolate5(prop, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix, valueSuffix) { const lView = getLView(); const interpolatedValue = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate5; } /** * * Update an interpolated style property on an element with 6 bound values surrounded by text. * * Used when the value passed to a property has 6 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstylePropInterpolate6(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix'); * ``` * * @param styleIndex Index of style to update. This index value refers to the * index of the style in the style bindings array that was passed into * `styling`. * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param suffix Static value used for concatenation only. * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵstylePropInterpolate6(prop, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix, valueSuffix) { const lView = getLView(); const interpolatedValue = interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate6; } /** * * Update an interpolated style property on an element with 7 bound values surrounded by text. * * Used when the value passed to a property has 7 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstylePropInterpolate7( * 0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix'); * ``` * * @param styleIndex Index of style to update. This index value refers to the * index of the style in the style bindings array that was passed into * `styling`. * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param i5 Static value used for concatenation only. * @param v6 Value checked for change. * @param suffix Static value used for concatenation only. * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵstylePropInterpolate7(prop, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix, valueSuffix) { const lView = getLView(); const interpolatedValue = interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate7; } /** * * Update an interpolated style property on an element with 8 bound values surrounded by text. * * Used when the value passed to a property has 8 interpolated values in it: * * ```html *
* ``` * * Its compiled representation is: * * ```ts * ɵɵstylePropInterpolate8(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, * '-', v7, 'suffix'); * ``` * * @param styleIndex Index of style to update. This index value refers to the * index of the style in the style bindings array that was passed into * `styling`. * @param prefix Static value used for concatenation only. * @param v0 Value checked for change. * @param i0 Static value used for concatenation only. * @param v1 Value checked for change. * @param i1 Static value used for concatenation only. * @param v2 Value checked for change. * @param i2 Static value used for concatenation only. * @param v3 Value checked for change. * @param i3 Static value used for concatenation only. * @param v4 Value checked for change. * @param i4 Static value used for concatenation only. * @param v5 Value checked for change. * @param i5 Static value used for concatenation only. * @param v6 Value checked for change. * @param i6 Static value used for concatenation only. * @param v7 Value checked for change. * @param suffix Static value used for concatenation only. * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵstylePropInterpolate8(prop, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix, valueSuffix) { const lView = getLView(); const interpolatedValue = interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolate8; } /** * Update an interpolated style property on an element with 9 or more bound values surrounded by * text. * * Used when the number of interpolated values exceeds 8. * * ```html *
*
* ``` * * Its compiled representation is: * * ```ts * ɵɵstylePropInterpolateV( * 0, ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9, * 'suffix']); * ``` * * @param styleIndex Index of style to update. This index value refers to the * index of the style in the style bindings array that was passed into * `styling`.. * @param values The collection of values and the strings in-between those values, beginning with * a string prefix and ending with a string suffix. * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`) * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. * @returns itself, so that it may be chained. * @codeGenApi */ function ɵɵstylePropInterpolateV(prop, values, valueSuffix) { const lView = getLView(); const interpolatedValue = interpolationV(lView, values); checkStylingProperty(prop, interpolatedValue, valueSuffix, false); return ɵɵstylePropInterpolateV; } /** * Update a property on a host element. Only applies to native node properties, not inputs. * * Operates on the element selected by index via the {@link select} instruction. * * @param propName Name of property. Because it is going to DOM, this is not subject to * renaming as part of minification. * @param value New value to write. * @param sanitizer An optional function used to sanitize the value. * @returns This function returns itself so that it may be chained * (e.g. `property('name', ctx.name)('title', ctx.title)`) * * @codeGenApi */ function ɵɵhostProperty(propName, value, sanitizer) { const lView = getLView(); const bindingIndex = nextBindingIndex(); if (bindingUpdated(lView, bindingIndex, value)) { const tView = getTView(); const tNode = getSelectedTNode(); elementPropertyInternal(tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, true); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex); } return ɵɵhostProperty; } /** * Updates a synthetic host binding (e.g. `[@foo]`) on a component or directive. * * This instruction is for compatibility purposes and is designed to ensure that a * synthetic host binding (e.g. `@HostBinding('@foo')`) properly gets rendered in * the component's renderer. Normally all host bindings are evaluated with the parent * component's renderer, but, in the case of animation @triggers, they need to be * evaluated with the sub component's renderer (because that's where the animation * triggers are defined). * * Do not use this instruction as a replacement for `elementProperty`. This instruction * only exists to ensure compatibility with the ViewEngine's host binding behavior. * * @param index The index of the element to update in the data array * @param propName Name of property. Because it is going to DOM, this is not subject to * renaming as part of minification. * @param value New value to write. * @param sanitizer An optional function used to sanitize the value. * * @codeGenApi */ function ɵɵsyntheticHostProperty(propName, value, sanitizer) { const lView = getLView(); const bindingIndex = nextBindingIndex(); if (bindingUpdated(lView, bindingIndex, value)) { const tView = getTView(); const tNode = getSelectedTNode(); const currentDef = getCurrentDirectiveDef(tView.data); const renderer = loadComponentRenderer(currentDef, tNode, lView); elementPropertyInternal(tView, tNode, lView, propName, value, renderer, sanitizer, true); ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex); } return ɵɵsyntheticHostProperty; } /** * NOTE: changes to the `ngI18nClosureMode` name must be synced with `compiler-cli/src/tooling.ts`. */ if (typeof ngI18nClosureMode === 'undefined') { // These property accesses can be ignored because ngI18nClosureMode will be set to false // when optimizing code and the whole if statement will be dropped. // Make sure to refer to ngI18nClosureMode as ['ngI18nClosureMode'] for closure. // NOTE: we need to have it in IIFE so that the tree-shaker is happy. (function () { // tslint:disable-next-line:no-toplevel-property-access _global['ngI18nClosureMode'] = // TODO(FW-1250): validate that this actually, you know, works. // tslint:disable-next-line:no-toplevel-property-access typeof goog !== 'undefined' && typeof goog.getMsg === 'function'; })(); } // THIS CODE IS GENERATED - DO NOT MODIFY. const u = undefined; function plural(val) { const i = Math.floor(Math.abs(val)), v = val.toString().replace(/^[^.]*\.?/, '').length; if (i === 1 && v === 0) return 1; return 5; } var localeEn = ["en", [["a", "p"], ["AM", "PM"], u], [["AM", "PM"], u, u], [["S", "M", "T", "W", "T", "F", "S"], ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]], u, [["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"], ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]], u, [["B", "A"], ["BC", "AD"], ["Before Christ", "Anno Domini"]], 0, [6, 0], ["M/d/yy", "MMM d, y", "MMMM d, y", "EEEE, MMMM d, y"], ["h:mm a", "h:mm:ss a", "h:mm:ss a z", "h:mm:ss a zzzz"], ["{1}, {0}", u, "{1} 'at' {0}", u], [".", ",", ";", "%", "+", "-", "E", "×", "‰", "∞", "NaN", ":"], ["#,##0.###", "#,##0%", "¤#,##0.00", "#E0"], "USD", "$", "US Dollar", {}, "ltr", plural]; /** * This const is used to store the locale data registered with `registerLocaleData` */ let LOCALE_DATA = {}; /** * Register locale data to be used internally by Angular. See the * ["I18n guide"](guide/i18n-common-format-data-locale) to know how to import additional locale * data. * * The signature `registerLocaleData(data: any, extraData?: any)` is deprecated since v5.1 */ function registerLocaleData(data, localeId, extraData) { if (typeof localeId !== 'string') { extraData = localeId; localeId = data[LocaleDataIndex.LocaleId]; } localeId = localeId.toLowerCase().replace(/_/g, '-'); LOCALE_DATA[localeId] = data; if (extraData) { LOCALE_DATA[localeId][LocaleDataIndex.ExtraData] = extraData; } } /** * Finds the locale data for a given locale. * * @param locale The locale code. * @returns The locale data. * @see [Internationalization (i18n) Guide](https://angular.io/guide/i18n-overview) */ function findLocaleData(locale) { const normalizedLocale = normalizeLocale(locale); let match = getLocaleData(normalizedLocale); if (match) { return match; } // let's try to find a parent locale const parentLocale = normalizedLocale.split('-')[0]; match = getLocaleData(parentLocale); if (match) { return match; } if (parentLocale === 'en') { return localeEn; } throw new RuntimeError(701 /* RuntimeErrorCode.MISSING_LOCALE_DATA */, ngDevMode && `Missing locale data for the locale "${locale}".`); } /** * Retrieves the default currency code for the given locale. * * The default is defined as the first currency which is still in use. * * @param locale The code of the locale whose currency code we want. * @returns The code of the default currency for the given locale. * */ function getLocaleCurrencyCode(locale) { const data = findLocaleData(locale); return data[LocaleDataIndex.CurrencyCode] || null; } /** * Retrieves the plural function used by ICU expressions to determine the plural case to use * for a given locale. * @param locale A locale code for the locale format rules to use. * @returns The plural function for the locale. * @see `NgPlural` * @see [Internationalization (i18n) Guide](/guide/i18n-overview) */ function getLocalePluralCase(locale) { const data = findLocaleData(locale); return data[LocaleDataIndex.PluralCase]; } /** * Helper function to get the given `normalizedLocale` from `LOCALE_DATA` * or from the global `ng.common.locale`. */ function getLocaleData(normalizedLocale) { if (!(normalizedLocale in LOCALE_DATA)) { LOCALE_DATA[normalizedLocale] = _global.ng && _global.ng.common && _global.ng.common.locales && _global.ng.common.locales[normalizedLocale]; } return LOCALE_DATA[normalizedLocale]; } /** * Helper function to remove all the locale data from `LOCALE_DATA`. */ function unregisterAllLocaleData() { LOCALE_DATA = {}; } /** * Index of each type of locale data from the locale data array */ var LocaleDataIndex; exports('ɵLocaleDataIndex', LocaleDataIndex); (function (LocaleDataIndex) { LocaleDataIndex[LocaleDataIndex["LocaleId"] = 0] = "LocaleId"; LocaleDataIndex[LocaleDataIndex["DayPeriodsFormat"] = 1] = "DayPeriodsFormat"; LocaleDataIndex[LocaleDataIndex["DayPeriodsStandalone"] = 2] = "DayPeriodsStandalone"; LocaleDataIndex[LocaleDataIndex["DaysFormat"] = 3] = "DaysFormat"; LocaleDataIndex[LocaleDataIndex["DaysStandalone"] = 4] = "DaysStandalone"; LocaleDataIndex[LocaleDataIndex["MonthsFormat"] = 5] = "MonthsFormat"; LocaleDataIndex[LocaleDataIndex["MonthsStandalone"] = 6] = "MonthsStandalone"; LocaleDataIndex[LocaleDataIndex["Eras"] = 7] = "Eras"; LocaleDataIndex[LocaleDataIndex["FirstDayOfWeek"] = 8] = "FirstDayOfWeek"; LocaleDataIndex[LocaleDataIndex["WeekendRange"] = 9] = "WeekendRange"; LocaleDataIndex[LocaleDataIndex["DateFormat"] = 10] = "DateFormat"; LocaleDataIndex[LocaleDataIndex["TimeFormat"] = 11] = "TimeFormat"; LocaleDataIndex[LocaleDataIndex["DateTimeFormat"] = 12] = "DateTimeFormat"; LocaleDataIndex[LocaleDataIndex["NumberSymbols"] = 13] = "NumberSymbols"; LocaleDataIndex[LocaleDataIndex["NumberFormats"] = 14] = "NumberFormats"; LocaleDataIndex[LocaleDataIndex["CurrencyCode"] = 15] = "CurrencyCode"; LocaleDataIndex[LocaleDataIndex["CurrencySymbol"] = 16] = "CurrencySymbol"; LocaleDataIndex[LocaleDataIndex["CurrencyName"] = 17] = "CurrencyName"; LocaleDataIndex[LocaleDataIndex["Currencies"] = 18] = "Currencies"; LocaleDataIndex[LocaleDataIndex["Directionality"] = 19] = "Directionality"; LocaleDataIndex[LocaleDataIndex["PluralCase"] = 20] = "PluralCase"; LocaleDataIndex[LocaleDataIndex["ExtraData"] = 21] = "ExtraData"; })(LocaleDataIndex || (exports('ɵLocaleDataIndex', LocaleDataIndex = {}))); /** * Returns the canonical form of a locale name - lowercase with `_` replaced with `-`. */ function normalizeLocale(locale) { return locale.toLowerCase().replace(/_/g, '-'); } const pluralMapping = ['zero', 'one', 'two', 'few', 'many']; /** * Returns the plural case based on the locale */ function getPluralCase(value, locale) { const plural = getLocalePluralCase(locale)(parseInt(value, 10)); const result = pluralMapping[plural]; return (result !== undefined) ? result : 'other'; } /** * The locale id that the application is using by default (for translations and ICU expressions). */ const DEFAULT_LOCALE_ID = exports('ɵDEFAULT_LOCALE_ID', 'en-US'); /** * USD currency code that the application uses by default for CurrencyPipe when no * DEFAULT_CURRENCY_CODE is provided. */ const USD_CURRENCY_CODE = 'USD'; /** * Marks that the next string is an element name. * * See `I18nMutateOpCodes` documentation. */ const ELEMENT_MARKER = { marker: 'element' }; /** * Marks that the next string is comment text need for ICU. * * See `I18nMutateOpCodes` documentation. */ const ICU_MARKER = { marker: 'ICU' }; /** * See `I18nCreateOpCodes` */ var I18nCreateOpCode; (function (I18nCreateOpCode) { /** * Number of bits to shift index so that it can be combined with the `APPEND_EAGERLY` and * `COMMENT`. */ I18nCreateOpCode[I18nCreateOpCode["SHIFT"] = 2] = "SHIFT"; /** * Should the node be appended to parent immediately after creation. */ I18nCreateOpCode[I18nCreateOpCode["APPEND_EAGERLY"] = 1] = "APPEND_EAGERLY"; /** * If set the node should be comment (rather than a text) node. */ I18nCreateOpCode[I18nCreateOpCode["COMMENT"] = 2] = "COMMENT"; })(I18nCreateOpCode || (I18nCreateOpCode = {})); /** * The locale id that the application is currently using (for translations and ICU expressions). * This is the ivy version of `LOCALE_ID` that was defined as an injection token for the view engine * but is now defined as a global value. */ let LOCALE_ID$1 = DEFAULT_LOCALE_ID; /** * Sets the locale id that will be used for translations and ICU expressions. * This is the ivy version of `LOCALE_ID` that was defined as an injection token for the view engine * but is now defined as a global value. * * @param localeId */ function setLocaleId(localeId) { assertDefined(localeId, `Expected localeId to be defined`); if (typeof localeId === 'string') { LOCALE_ID$1 = localeId.toLowerCase().replace(/_/g, '-'); } } /** * Gets the locale id that will be used for translations and ICU expressions. * This is the ivy version of `LOCALE_ID` that was defined as an injection token for the view engine * but is now defined as a global value. */ function getLocaleId() { return LOCALE_ID$1; } /** * Find a node in front of which `currentTNode` should be inserted (takes i18n into account). * * This method determines the `RNode` in front of which we should insert the `currentRNode`. This * takes `TNode.insertBeforeIndex` into account. * * @param parentTNode parent `TNode` * @param currentTNode current `TNode` (The node which we would like to insert into the DOM) * @param lView current `LView` */ function getInsertInFrontOfRNodeWithI18n(parentTNode, currentTNode, lView) { const tNodeInsertBeforeIndex = currentTNode.insertBeforeIndex; const insertBeforeIndex = Array.isArray(tNodeInsertBeforeIndex) ? tNodeInsertBeforeIndex[0] : tNodeInsertBeforeIndex; if (insertBeforeIndex === null) { return getInsertInFrontOfRNodeWithNoI18n(parentTNode, currentTNode, lView); } else { ngDevMode && assertIndexInRange(lView, insertBeforeIndex); return unwrapRNode(lView[insertBeforeIndex]); } } /** * Process `TNode.insertBeforeIndex` by adding i18n text nodes. * * See `TNode.insertBeforeIndex` */ function processI18nInsertBefore(renderer, childTNode, lView, childRNode, parentRElement) { const tNodeInsertBeforeIndex = childTNode.insertBeforeIndex; if (Array.isArray(tNodeInsertBeforeIndex)) { // An array indicates that there are i18n nodes that need to be added as children of this // `childRNode`. These i18n nodes were created before this `childRNode` was available and so // only now can be added. The first element of the array is the normal index where we should // insert the `childRNode`. Additional elements are the extra nodes to be added as children of // `childRNode`. ngDevMode && assertDomNode(childRNode); let i18nParent = childRNode; let anchorRNode = null; if (!(childTNode.type & 3 /* TNodeType.AnyRNode */)) { anchorRNode = i18nParent; i18nParent = parentRElement; } if (i18nParent !== null && childTNode.componentOffset === -1) { for (let i = 1; i < tNodeInsertBeforeIndex.length; i++) { // No need to `unwrapRNode` because all of the indexes point to i18n text nodes. // see `assertDomNode` below. const i18nChild = lView[tNodeInsertBeforeIndex[i]]; nativeInsertBefore(renderer, i18nParent, i18nChild, anchorRNode, false); } } } } /** * Add `tNode` to `previousTNodes` list and update relevant `TNode`s in `previousTNodes` list * `tNode.insertBeforeIndex`. * * Things to keep in mind: * 1. All i18n text nodes are encoded as `TNodeType.Element` and are created eagerly by the * `ɵɵi18nStart` instruction. * 2. All `TNodeType.Placeholder` `TNodes` are elements which will be created later by * `ɵɵelementStart` instruction. * 3. `ɵɵelementStart` instruction will create `TNode`s in the ascending `TNode.index` order. (So a * smaller index `TNode` is guaranteed to be created before a larger one) * * We use the above three invariants to determine `TNode.insertBeforeIndex`. * * In an ideal world `TNode.insertBeforeIndex` would always be `TNode.next.index`. However, * this will not work because `TNode.next.index` may be larger than `TNode.index` which means that * the next node is not yet created and therefore we can't insert in front of it. * * Rule1: `TNode.insertBeforeIndex = null` if `TNode.next === null` (Initial condition, as we don't * know if there will be further `TNode`s inserted after.) * Rule2: If `previousTNode` is created after the `tNode` being inserted, then * `previousTNode.insertBeforeNode = tNode.index` (So when a new `tNode` is added we check * previous to see if we can update its `insertBeforeTNode`) * * See `TNode.insertBeforeIndex` for more context. * * @param previousTNodes A list of previous TNodes so that we can easily traverse `TNode`s in * reverse order. (If `TNode` would have `previous` this would not be necessary.) * @param newTNode A TNode to add to the `previousTNodes` list. */ function addTNodeAndUpdateInsertBeforeIndex(previousTNodes, newTNode) { // Start with Rule1 ngDevMode && assertEqual(newTNode.insertBeforeIndex, null, 'We expect that insertBeforeIndex is not set'); previousTNodes.push(newTNode); if (previousTNodes.length > 1) { for (let i = previousTNodes.length - 2; i >= 0; i--) { const existingTNode = previousTNodes[i]; // Text nodes are created eagerly and so they don't need their `indexBeforeIndex` updated. // It is safe to ignore them. if (!isI18nText(existingTNode)) { if (isNewTNodeCreatedBefore(existingTNode, newTNode) && getInsertBeforeIndex(existingTNode) === null) { // If it was created before us in time, (and it does not yet have `insertBeforeIndex`) // then add the `insertBeforeIndex`. setInsertBeforeIndex(existingTNode, newTNode.index); } } } } } function isI18nText(tNode) { return !(tNode.type & 64 /* TNodeType.Placeholder */); } function isNewTNodeCreatedBefore(existingTNode, newTNode) { return isI18nText(newTNode) || existingTNode.index > newTNode.index; } function getInsertBeforeIndex(tNode) { const index = tNode.insertBeforeIndex; return Array.isArray(index) ? index[0] : index; } function setInsertBeforeIndex(tNode, value) { const index = tNode.insertBeforeIndex; if (Array.isArray(index)) { // Array is stored if we have to insert child nodes. See `TNode.insertBeforeIndex` index[0] = value; } else { setI18nHandling(getInsertInFrontOfRNodeWithI18n, processI18nInsertBefore); tNode.insertBeforeIndex = value; } } /** * Retrieve `TIcu` at a given `index`. * * The `TIcu` can be stored either directly (if it is nested ICU) OR * it is stored inside tho `TIcuContainer` if it is top level ICU. * * The reason for this is that the top level ICU need a `TNode` so that they are part of the render * tree, but nested ICU's have no TNode, because we don't know ahead of time if the nested ICU is * expressed (parent ICU may have selected a case which does not contain it.) * * @param tView Current `TView`. * @param index Index where the value should be read from. */ function getTIcu(tView, index) { const value = tView.data[index]; if (value === null || typeof value === 'string') return null; if (ngDevMode && !(value.hasOwnProperty('tView') || value.hasOwnProperty('currentCaseLViewIndex'))) { throwError('We expect to get \'null\'|\'TIcu\'|\'TIcuContainer\', but got: ' + value); } // Here the `value.hasOwnProperty('currentCaseLViewIndex')` is a polymorphic read as it can be // either TIcu or TIcuContainerNode. This is not ideal, but we still think it is OK because it // will be just two cases which fits into the browser inline cache (inline cache can take up to // 4) const tIcu = value.hasOwnProperty('currentCaseLViewIndex') ? value : value.value; ngDevMode && assertTIcu(tIcu); return tIcu; } /** * Store `TIcu` at a give `index`. * * The `TIcu` can be stored either directly (if it is nested ICU) OR * it is stored inside tho `TIcuContainer` if it is top level ICU. * * The reason for this is that the top level ICU need a `TNode` so that they are part of the render * tree, but nested ICU's have no TNode, because we don't know ahead of time if the nested ICU is * expressed (parent ICU may have selected a case which does not contain it.) * * @param tView Current `TView`. * @param index Index where the value should be stored at in `Tview.data` * @param tIcu The TIcu to store. */ function setTIcu(tView, index, tIcu) { const tNode = tView.data[index]; ngDevMode && assertEqual(tNode === null || tNode.hasOwnProperty('tView'), true, 'We expect to get \'null\'|\'TIcuContainer\''); if (tNode === null) { tView.data[index] = tIcu; } else { ngDevMode && assertTNodeType(tNode, 32 /* TNodeType.Icu */); tNode.value = tIcu; } } /** * Set `TNode.insertBeforeIndex` taking the `Array` into account. * * See `TNode.insertBeforeIndex` */ function setTNodeInsertBeforeIndex(tNode, index) { ngDevMode && assertTNode(tNode); let insertBeforeIndex = tNode.insertBeforeIndex; if (insertBeforeIndex === null) { setI18nHandling(getInsertInFrontOfRNodeWithI18n, processI18nInsertBefore); insertBeforeIndex = tNode.insertBeforeIndex = [null /* may be updated to number later */, index]; } else { assertEqual(Array.isArray(insertBeforeIndex), true, 'Expecting array here'); insertBeforeIndex.push(index); } } /** * Create `TNode.type=TNodeType.Placeholder` node. * * See `TNodeType.Placeholder` for more information. */ function createTNodePlaceholder(tView, previousTNodes, index) { const tNode = createTNodeAtIndex(tView, index, 64 /* TNodeType.Placeholder */, null, null); addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tNode); return tNode; } /** * Returns current ICU case. * * ICU cases are stored as index into the `TIcu.cases`. * At times it is necessary to communicate that the ICU case just switched and that next ICU update * should update all bindings regardless of the mask. In such a case the we store negative numbers * for cases which have just been switched. This function removes the negative flag. */ function getCurrentICUCaseIndex(tIcu, lView) { const currentCase = lView[tIcu.currentCaseLViewIndex]; return currentCase === null ? currentCase : (currentCase < 0 ? ~currentCase : currentCase); } function getParentFromIcuCreateOpCode(mergedCode) { return mergedCode >>> 17 /* IcuCreateOpCode.SHIFT_PARENT */; } function getRefFromIcuCreateOpCode(mergedCode) { return (mergedCode & 131070 /* IcuCreateOpCode.MASK_REF */) >>> 1 /* IcuCreateOpCode.SHIFT_REF */; } function getInstructionFromIcuCreateOpCode(mergedCode) { return mergedCode & 1 /* IcuCreateOpCode.MASK_INSTRUCTION */; } function icuCreateOpCode(opCode, parentIdx, refIdx) { ngDevMode && assertGreaterThanOrEqual(parentIdx, 0, 'Missing parent index'); ngDevMode && assertGreaterThan(refIdx, 0, 'Missing ref index'); return opCode | parentIdx << 17 /* IcuCreateOpCode.SHIFT_PARENT */ | refIdx << 1 /* IcuCreateOpCode.SHIFT_REF */; } /** * Keep track of which input bindings in `ɵɵi18nExp` have changed. * * This is used to efficiently update expressions in i18n only when the corresponding input has * changed. * * 1) Each bit represents which of the `ɵɵi18nExp` has changed. * 2) There are 32 bits allowed in JS. * 3) Bit 32 is special as it is shared for all changes past 32. (In other words if you have more * than 32 `ɵɵi18nExp` then all changes past 32nd `ɵɵi18nExp` will be mapped to same bit. This means * that we may end up changing more than we need to. But i18n expressions with 32 bindings is rare * so in practice it should not be an issue.) */ let changeMask = 0b0; /** * Keeps track of which bit needs to be updated in `changeMask` * * This value gets incremented on every call to `ɵɵi18nExp` */ let changeMaskCounter = 0; /** * Keep track of which input bindings in `ɵɵi18nExp` have changed. * * `setMaskBit` gets invoked by each call to `ɵɵi18nExp`. * * @param hasChange did `ɵɵi18nExp` detect a change. */ function setMaskBit(hasChange) { if (hasChange) { changeMask = changeMask | (1 << Math.min(changeMaskCounter, 31)); } changeMaskCounter++; } function applyI18n(tView, lView, index) { if (changeMaskCounter > 0) { ngDevMode && assertDefined(tView, `tView should be defined`); const tI18n = tView.data[index]; // When `index` points to an `ɵɵi18nAttributes` then we have an array otherwise `TI18n` const updateOpCodes = Array.isArray(tI18n) ? tI18n : tI18n.update; const bindingsStartIndex = getBindingIndex() - changeMaskCounter - 1; applyUpdateOpCodes(tView, lView, updateOpCodes, bindingsStartIndex, changeMask); } // Reset changeMask & maskBit to default for the next update cycle changeMask = 0b0; changeMaskCounter = 0; } /** * Apply `I18nCreateOpCodes` op-codes as stored in `TI18n.create`. * * Creates text (and comment) nodes which are internationalized. * * @param lView Current lView * @param createOpCodes Set of op-codes to apply * @param parentRNode Parent node (so that direct children can be added eagerly) or `null` if it is * a root node. * @param insertInFrontOf DOM node that should be used as an anchor. */ function applyCreateOpCodes(lView, createOpCodes, parentRNode, insertInFrontOf) { const renderer = lView[RENDERER]; for (let i = 0; i < createOpCodes.length; i++) { const opCode = createOpCodes[i++]; const text = createOpCodes[i]; const isComment = (opCode & I18nCreateOpCode.COMMENT) === I18nCreateOpCode.COMMENT; const appendNow = (opCode & I18nCreateOpCode.APPEND_EAGERLY) === I18nCreateOpCode.APPEND_EAGERLY; const index = opCode >>> I18nCreateOpCode.SHIFT; let rNode = lView[index]; if (rNode === null) { // We only create new DOM nodes if they don't already exist: If ICU switches case back to a // case which was already instantiated, no need to create new DOM nodes. rNode = lView[index] = isComment ? renderer.createComment(text) : createTextNode(renderer, text); } if (appendNow && parentRNode !== null) { nativeInsertBefore(renderer, parentRNode, rNode, insertInFrontOf, false); } } } /** * Apply `I18nMutateOpCodes` OpCodes. * * @param tView Current `TView` * @param mutableOpCodes Mutable OpCodes to process * @param lView Current `LView` * @param anchorRNode place where the i18n node should be inserted. */ function applyMutableOpCodes(tView, mutableOpCodes, lView, anchorRNode) { ngDevMode && assertDomNode(anchorRNode); const renderer = lView[RENDERER]; // `rootIdx` represents the node into which all inserts happen. let rootIdx = null; // `rootRNode` represents the real node into which we insert. This can be different from // `lView[rootIdx]` if we have projection. // - null we don't have a parent (as can be the case in when we are inserting into a root of // LView which has no parent.) // - `RElement` The element representing the root after taking projection into account. let rootRNode; for (let i = 0; i < mutableOpCodes.length; i++) { const opCode = mutableOpCodes[i]; if (typeof opCode == 'string') { const textNodeIndex = mutableOpCodes[++i]; if (lView[textNodeIndex] === null) { ngDevMode && ngDevMode.rendererCreateTextNode++; ngDevMode && assertIndexInRange(lView, textNodeIndex); lView[textNodeIndex] = createTextNode(renderer, opCode); } } else if (typeof opCode == 'number') { switch (opCode & 1 /* IcuCreateOpCode.MASK_INSTRUCTION */) { case 0 /* IcuCreateOpCode.AppendChild */: const parentIdx = getParentFromIcuCreateOpCode(opCode); if (rootIdx === null) { // The first operation should save the `rootIdx` because the first operation // must insert into the root. (Only subsequent operations can insert into a dynamic // parent) rootIdx = parentIdx; rootRNode = nativeParentNode(renderer, anchorRNode); } let insertInFrontOf; let parentRNode; if (parentIdx === rootIdx) { insertInFrontOf = anchorRNode; parentRNode = rootRNode; } else { insertInFrontOf = null; parentRNode = unwrapRNode(lView[parentIdx]); } // FIXME(misko): Refactor with `processI18nText` if (parentRNode !== null) { // This can happen if the `LView` we are adding to is not attached to a parent `LView`. // In such a case there is no "root" we can attach to. This is fine, as we still need to // create the elements. When the `LView` gets later added to a parent these "root" nodes // get picked up and added. ngDevMode && assertDomNode(parentRNode); const refIdx = getRefFromIcuCreateOpCode(opCode); ngDevMode && assertGreaterThan(refIdx, HEADER_OFFSET, 'Missing ref'); // `unwrapRNode` is not needed here as all of these point to RNodes as part of the i18n // which can't have components. const child = lView[refIdx]; ngDevMode && assertDomNode(child); nativeInsertBefore(renderer, parentRNode, child, insertInFrontOf, false); const tIcu = getTIcu(tView, refIdx); if (tIcu !== null && typeof tIcu === 'object') { // If we just added a comment node which has ICU then that ICU may have already been // rendered and therefore we need to re-add it here. ngDevMode && assertTIcu(tIcu); const caseIndex = getCurrentICUCaseIndex(tIcu, lView); if (caseIndex !== null) { applyMutableOpCodes(tView, tIcu.create[caseIndex], lView, lView[tIcu.anchorIdx]); } } } break; case 1 /* IcuCreateOpCode.Attr */: const elementNodeIndex = opCode >>> 1 /* IcuCreateOpCode.SHIFT_REF */; const attrName = mutableOpCodes[++i]; const attrValue = mutableOpCodes[++i]; // This code is used for ICU expressions only, since we don't support // directives/components in ICUs, we don't need to worry about inputs here setElementAttribute(renderer, getNativeByIndex(elementNodeIndex, lView), null, null, attrName, attrValue, null); break; default: if (ngDevMode) { throw new RuntimeError(700 /* RuntimeErrorCode.INVALID_I18N_STRUCTURE */, `Unable to determine the type of mutate operation for "${opCode}"`); } } } else { switch (opCode) { case ICU_MARKER: const commentValue = mutableOpCodes[++i]; const commentNodeIndex = mutableOpCodes[++i]; if (lView[commentNodeIndex] === null) { ngDevMode && assertEqual(typeof commentValue, 'string', `Expected "${commentValue}" to be a comment node value`); ngDevMode && ngDevMode.rendererCreateComment++; ngDevMode && assertIndexInExpandoRange(lView, commentNodeIndex); const commentRNode = lView[commentNodeIndex] = createCommentNode(renderer, commentValue); // FIXME(misko): Attaching patch data is only needed for the root (Also add tests) attachPatchData(commentRNode, lView); } break; case ELEMENT_MARKER: const tagName = mutableOpCodes[++i]; const elementNodeIndex = mutableOpCodes[++i]; if (lView[elementNodeIndex] === null) { ngDevMode && assertEqual(typeof tagName, 'string', `Expected "${tagName}" to be an element node tag name`); ngDevMode && ngDevMode.rendererCreateElement++; ngDevMode && assertIndexInExpandoRange(lView, elementNodeIndex); const elementRNode = lView[elementNodeIndex] = createElementNode(renderer, tagName, null); // FIXME(misko): Attaching patch data is only needed for the root (Also add tests) attachPatchData(elementRNode, lView); } break; default: ngDevMode && throwError(`Unable to determine the type of mutate operation for "${opCode}"`); } } } } /** * Apply `I18nUpdateOpCodes` OpCodes * * @param tView Current `TView` * @param lView Current `LView` * @param updateOpCodes OpCodes to process * @param bindingsStartIndex Location of the first `ɵɵi18nApply` * @param changeMask Each bit corresponds to a `ɵɵi18nExp` (Counting backwards from * `bindingsStartIndex`) */ function applyUpdateOpCodes(tView, lView, updateOpCodes, bindingsStartIndex, changeMask) { for (let i = 0; i < updateOpCodes.length; i++) { // bit code to check if we should apply the next update const checkBit = updateOpCodes[i]; // Number of opCodes to skip until next set of update codes const skipCodes = updateOpCodes[++i]; if (checkBit & changeMask) { // The value has been updated since last checked let value = ''; for (let j = i + 1; j <= (i + skipCodes); j++) { const opCode = updateOpCodes[j]; if (typeof opCode == 'string') { value += opCode; } else if (typeof opCode == 'number') { if (opCode < 0) { // Negative opCode represent `i18nExp` values offset. value += renderStringify(lView[bindingsStartIndex - opCode]); } else { const nodeIndex = (opCode >>> 2 /* I18nUpdateOpCode.SHIFT_REF */); switch (opCode & 3 /* I18nUpdateOpCode.MASK_OPCODE */) { case 1 /* I18nUpdateOpCode.Attr */: const propName = updateOpCodes[++j]; const sanitizeFn = updateOpCodes[++j]; const tNodeOrTagName = tView.data[nodeIndex]; ngDevMode && assertDefined(tNodeOrTagName, 'Experting TNode or string'); if (typeof tNodeOrTagName === 'string') { // IF we don't have a `TNode`, then we are an element in ICU (as ICU content does // not have TNode), in which case we know that there are no directives, and hence // we use attribute setting. setElementAttribute(lView[RENDERER], lView[nodeIndex], null, tNodeOrTagName, propName, value, sanitizeFn); } else { elementPropertyInternal(tView, tNodeOrTagName, lView, propName, value, lView[RENDERER], sanitizeFn, false); } break; case 0 /* I18nUpdateOpCode.Text */: const rText = lView[nodeIndex]; rText !== null && updateTextNode(lView[RENDERER], rText, value); break; case 2 /* I18nUpdateOpCode.IcuSwitch */: applyIcuSwitchCase(tView, getTIcu(tView, nodeIndex), lView, value); break; case 3 /* I18nUpdateOpCode.IcuUpdate */: applyIcuUpdateCase(tView, getTIcu(tView, nodeIndex), bindingsStartIndex, lView); break; } } } } } else { const opCode = updateOpCodes[i + 1]; if (opCode > 0 && (opCode & 3 /* I18nUpdateOpCode.MASK_OPCODE */) === 3 /* I18nUpdateOpCode.IcuUpdate */) { // Special case for the `icuUpdateCase`. It could be that the mask did not match, but // we still need to execute `icuUpdateCase` because the case has changed recently due to // previous `icuSwitchCase` instruction. (`icuSwitchCase` and `icuUpdateCase` always come in // pairs.) const nodeIndex = (opCode >>> 2 /* I18nUpdateOpCode.SHIFT_REF */); const tIcu = getTIcu(tView, nodeIndex); const currentIndex = lView[tIcu.currentCaseLViewIndex]; if (currentIndex < 0) { applyIcuUpdateCase(tView, tIcu, bindingsStartIndex, lView); } } } i += skipCodes; } } /** * Apply OpCodes associated with updating an existing ICU. * * @param tView Current `TView` * @param tIcu Current `TIcu` * @param bindingsStartIndex Location of the first `ɵɵi18nApply` * @param lView Current `LView` */ function applyIcuUpdateCase(tView, tIcu, bindingsStartIndex, lView) { ngDevMode && assertIndexInRange(lView, tIcu.currentCaseLViewIndex); let activeCaseIndex = lView[tIcu.currentCaseLViewIndex]; if (activeCaseIndex !== null) { let mask = changeMask; if (activeCaseIndex < 0) { // Clear the flag. // Negative number means that the ICU was freshly created and we need to force the update. activeCaseIndex = lView[tIcu.currentCaseLViewIndex] = ~activeCaseIndex; // -1 is same as all bits on, which simulates creation since it marks all bits dirty mask = -1; } applyUpdateOpCodes(tView, lView, tIcu.update[activeCaseIndex], bindingsStartIndex, mask); } } /** * Apply OpCodes associated with switching a case on ICU. * * This involves tearing down existing case and than building up a new case. * * @param tView Current `TView` * @param tIcu Current `TIcu` * @param lView Current `LView` * @param value Value of the case to update to. */ function applyIcuSwitchCase(tView, tIcu, lView, value) { // Rebuild a new case for this ICU const caseIndex = getCaseIndex(tIcu, value); let activeCaseIndex = getCurrentICUCaseIndex(tIcu, lView); if (activeCaseIndex !== caseIndex) { applyIcuSwitchCaseRemove(tView, tIcu, lView); lView[tIcu.currentCaseLViewIndex] = caseIndex === null ? null : ~caseIndex; if (caseIndex !== null) { // Add the nodes for the new case const anchorRNode = lView[tIcu.anchorIdx]; if (anchorRNode) { ngDevMode && assertDomNode(anchorRNode); applyMutableOpCodes(tView, tIcu.create[caseIndex], lView, anchorRNode); } } } } /** * Apply OpCodes associated with tearing ICU case. * * This involves tearing down existing case and than building up a new case. * * @param tView Current `TView` * @param tIcu Current `TIcu` * @param lView Current `LView` */ function applyIcuSwitchCaseRemove(tView, tIcu, lView) { let activeCaseIndex = getCurrentICUCaseIndex(tIcu, lView); if (activeCaseIndex !== null) { const removeCodes = tIcu.remove[activeCaseIndex]; for (let i = 0; i < removeCodes.length; i++) { const nodeOrIcuIndex = removeCodes[i]; if (nodeOrIcuIndex > 0) { // Positive numbers are `RNode`s. const rNode = getNativeByIndex(nodeOrIcuIndex, lView); rNode !== null && nativeRemoveNode(lView[RENDERER], rNode); } else { // Negative numbers are ICUs applyIcuSwitchCaseRemove(tView, getTIcu(tView, ~nodeOrIcuIndex), lView); } } } } /** * Returns the index of the current case of an ICU expression depending on the main binding value * * @param icuExpression * @param bindingValue The value of the main binding used by this ICU expression */ function getCaseIndex(icuExpression, bindingValue) { let index = icuExpression.cases.indexOf(bindingValue); if (index === -1) { switch (icuExpression.type) { case 1 /* IcuType.plural */: { const resolvedCase = getPluralCase(bindingValue, getLocaleId()); index = icuExpression.cases.indexOf(resolvedCase); if (index === -1 && resolvedCase !== 'other') { index = icuExpression.cases.indexOf('other'); } break; } case 0 /* IcuType.select */: { index = icuExpression.cases.indexOf('other'); break; } } } return index === -1 ? null : index; } function loadIcuContainerVisitor() { const _stack = []; let _index = -1; let _lView; let _removes; /** * Retrieves a set of root nodes from `TIcu.remove`. Used by `TNodeType.ICUContainer` * to determine which root belong to the ICU. * * Example of usage. * ``` * const nextRNode = icuContainerIteratorStart(tIcuContainerNode, lView); * let rNode: RNode|null; * while(rNode = nextRNode()) { * console.log(rNode); * } * ``` * * @param tIcuContainerNode Current `TIcuContainerNode` * @param lView `LView` where the `RNode`s should be looked up. */ function icuContainerIteratorStart(tIcuContainerNode, lView) { _lView = lView; while (_stack.length) _stack.pop(); ngDevMode && assertTNodeForLView(tIcuContainerNode, lView); enterIcu(tIcuContainerNode.value, lView); return icuContainerIteratorNext; } function enterIcu(tIcu, lView) { _index = 0; const currentCase = getCurrentICUCaseIndex(tIcu, lView); if (currentCase !== null) { ngDevMode && assertNumberInRange(currentCase, 0, tIcu.cases.length - 1); _removes = tIcu.remove[currentCase]; } else { _removes = EMPTY_ARRAY; } } function icuContainerIteratorNext() { if (_index < _removes.length) { const removeOpCode = _removes[_index++]; ngDevMode && assertNumber(removeOpCode, 'Expecting OpCode number'); if (removeOpCode > 0) { const rNode = _lView[removeOpCode]; ngDevMode && assertDomNode(rNode); return rNode; } else { _stack.push(_index, _removes); // ICUs are represented by negative indices const tIcuIndex = ~removeOpCode; const tIcu = _lView[TVIEW].data[tIcuIndex]; ngDevMode && assertTIcu(tIcu); enterIcu(tIcu, _lView); return icuContainerIteratorNext(); } } else { if (_stack.length === 0) { return null; } else { _removes = _stack.pop(); _index = _stack.pop(); return icuContainerIteratorNext(); } } } return icuContainerIteratorStart; } /** * Converts `I18nCreateOpCodes` array into a human readable format. * * This function is attached to the `I18nCreateOpCodes.debug` property if `ngDevMode` is enabled. * This function provides a human readable view of the opcodes. This is useful when debugging the * application as well as writing more readable tests. * * @param this `I18nCreateOpCodes` if attached as a method. * @param opcodes `I18nCreateOpCodes` if invoked as a function. */ function i18nCreateOpCodesToString(opcodes) { const createOpCodes = opcodes || (Array.isArray(this) ? this : []); let lines = []; for (let i = 0; i < createOpCodes.length; i++) { const opCode = createOpCodes[i++]; const text = createOpCodes[i]; const isComment = (opCode & I18nCreateOpCode.COMMENT) === I18nCreateOpCode.COMMENT; const appendNow = (opCode & I18nCreateOpCode.APPEND_EAGERLY) === I18nCreateOpCode.APPEND_EAGERLY; const index = opCode >>> I18nCreateOpCode.SHIFT; lines.push(`lView[${index}] = document.${isComment ? 'createComment' : 'createText'}(${JSON.stringify(text)});`); if (appendNow) { lines.push(`parent.appendChild(lView[${index}]);`); } } return lines; } /** * Converts `I18nUpdateOpCodes` array into a human readable format. * * This function is attached to the `I18nUpdateOpCodes.debug` property if `ngDevMode` is enabled. * This function provides a human readable view of the opcodes. This is useful when debugging the * application as well as writing more readable tests. * * @param this `I18nUpdateOpCodes` if attached as a method. * @param opcodes `I18nUpdateOpCodes` if invoked as a function. */ function i18nUpdateOpCodesToString(opcodes) { const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : [])); let lines = []; function consumeOpCode(value) { const ref = value >>> 2 /* I18nUpdateOpCode.SHIFT_REF */; const opCode = value & 3 /* I18nUpdateOpCode.MASK_OPCODE */; switch (opCode) { case 0 /* I18nUpdateOpCode.Text */: return `(lView[${ref}] as Text).textContent = $$$`; case 1 /* I18nUpdateOpCode.Attr */: const attrName = parser.consumeString(); const sanitizationFn = parser.consumeFunction(); const value = sanitizationFn ? `(${sanitizationFn})($$$)` : '$$$'; return `(lView[${ref}] as Element).setAttribute('${attrName}', ${value})`; case 2 /* I18nUpdateOpCode.IcuSwitch */: return `icuSwitchCase(${ref}, $$$)`; case 3 /* I18nUpdateOpCode.IcuUpdate */: return `icuUpdateCase(${ref})`; } throw new Error('unexpected OpCode'); } while (parser.hasMore()) { let mask = parser.consumeNumber(); let size = parser.consumeNumber(); const end = parser.i + size; const statements = []; let statement = ''; while (parser.i < end) { let value = parser.consumeNumberOrString(); if (typeof value === 'string') { statement += value; } else if (value < 0) { // Negative numbers are ref indexes // Here `i` refers to current binding index. It is to signify that the value is relative, // rather than absolute. statement += '${lView[i' + value + ']}'; } else { // Positive numbers are operations. const opCodeText = consumeOpCode(value); statements.push(opCodeText.replace('$$$', '`' + statement + '`') + ';'); statement = ''; } } lines.push(`if (mask & 0b${mask.toString(2)}) { ${statements.join(' ')} }`); } return lines; } /** * Converts `I18nCreateOpCodes` array into a human readable format. * * This function is attached to the `I18nCreateOpCodes.debug` if `ngDevMode` is enabled. This * function provides a human readable view of the opcodes. This is useful when debugging the * application as well as writing more readable tests. * * @param this `I18nCreateOpCodes` if attached as a method. * @param opcodes `I18nCreateOpCodes` if invoked as a function. */ function icuCreateOpCodesToString(opcodes) { const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : [])); let lines = []; function consumeOpCode(opCode) { const parent = getParentFromIcuCreateOpCode(opCode); const ref = getRefFromIcuCreateOpCode(opCode); switch (getInstructionFromIcuCreateOpCode(opCode)) { case 0 /* IcuCreateOpCode.AppendChild */: return `(lView[${parent}] as Element).appendChild(lView[${lastRef}])`; case 1 /* IcuCreateOpCode.Attr */: return `(lView[${ref}] as Element).setAttribute("${parser.consumeString()}", "${parser.consumeString()}")`; } throw new Error('Unexpected OpCode: ' + getInstructionFromIcuCreateOpCode(opCode)); } let lastRef = -1; while (parser.hasMore()) { let value = parser.consumeNumberStringOrMarker(); if (value === ICU_MARKER) { const text = parser.consumeString(); lastRef = parser.consumeNumber(); lines.push(`lView[${lastRef}] = document.createComment("${text}")`); } else if (value === ELEMENT_MARKER) { const text = parser.consumeString(); lastRef = parser.consumeNumber(); lines.push(`lView[${lastRef}] = document.createElement("${text}")`); } else if (typeof value === 'string') { lastRef = parser.consumeNumber(); lines.push(`lView[${lastRef}] = document.createTextNode("${value}")`); } else if (typeof value === 'number') { const line = consumeOpCode(value); line && lines.push(line); } else { throw new Error('Unexpected value'); } } return lines; } /** * Converts `I18nRemoveOpCodes` array into a human readable format. * * This function is attached to the `I18nRemoveOpCodes.debug` if `ngDevMode` is enabled. This * function provides a human readable view of the opcodes. This is useful when debugging the * application as well as writing more readable tests. * * @param this `I18nRemoveOpCodes` if attached as a method. * @param opcodes `I18nRemoveOpCodes` if invoked as a function. */ function i18nRemoveOpCodesToString(opcodes) { const removeCodes = opcodes || (Array.isArray(this) ? this : []); let lines = []; for (let i = 0; i < removeCodes.length; i++) { const nodeOrIcuIndex = removeCodes[i]; if (nodeOrIcuIndex > 0) { // Positive numbers are `RNode`s. lines.push(`remove(lView[${nodeOrIcuIndex}])`); } else { // Negative numbers are ICUs lines.push(`removeNestedICU(${~nodeOrIcuIndex})`); } } return lines; } class OpCodeParser { constructor(codes) { this.i = 0; this.codes = codes; } hasMore() { return this.i < this.codes.length; } consumeNumber() { let value = this.codes[this.i++]; assertNumber(value, 'expecting number in OpCode'); return value; } consumeString() { let value = this.codes[this.i++]; assertString(value, 'expecting string in OpCode'); return value; } consumeFunction() { let value = this.codes[this.i++]; if (value === null || typeof value === 'function') { return value; } throw new Error('expecting function in OpCode'); } consumeNumberOrString() { let value = this.codes[this.i++]; if (typeof value === 'string') { return value; } assertNumber(value, 'expecting number or string in OpCode'); return value; } consumeNumberStringOrMarker() { let value = this.codes[this.i++]; if (typeof value === 'string' || typeof value === 'number' || value == ICU_MARKER || value == ELEMENT_MARKER) { return value; } assertNumber(value, 'expecting number, string, ICU_MARKER or ELEMENT_MARKER in OpCode'); return value; } } const BINDING_REGEXP = /�(\d+):?\d*�/gi; const ICU_REGEXP = /({\s*�\d+:?\d*�\s*,\s*\S{6}\s*,[\s\S]*})/gi; const NESTED_ICU = /�(\d+)�/; const ICU_BLOCK_REGEXP = /^\s*(�\d+:?\d*�)\s*,\s*(select|plural)\s*,/; const MARKER = `�`; const SUBTEMPLATE_REGEXP = /�\/?\*(\d+:\d+)�/gi; const PH_REGEXP = /�(\/?[#*]\d+):?\d*�/gi; /** * Angular uses the special entity &ngsp; as a placeholder for non-removable space. * It's replaced by the 0xE500 PUA (Private Use Areas) unicode character and later on replaced by a * space. * We are re-implementing the same idea since translations might contain this special character. */ const NGSP_UNICODE_REGEXP = /\uE500/g; function replaceNgsp(value) { return value.replace(NGSP_UNICODE_REGEXP, ' '); } /** * Patch a `debug` property getter on top of the existing object. * * NOTE: always call this method with `ngDevMode && attachDebugObject(...)` * * @param obj Object to patch * @param debugGetter Getter returning a value to patch */ function attachDebugGetter(obj, debugGetter) { if (ngDevMode) { Object.defineProperty(obj, 'debug', { get: debugGetter, enumerable: false }); } else { throw new Error('This method should be guarded with `ngDevMode` so that it can be tree shaken in production!'); } } /** * Create dynamic nodes from i18n translation block. * * - Text nodes are created synchronously * - TNodes are linked into tree lazily * * @param tView Current `TView` * @parentTNodeIndex index to the parent TNode of this i18n block * @param lView Current `LView` * @param index Index of `ɵɵi18nStart` instruction. * @param message Message to translate. * @param subTemplateIndex Index into the sub template of message translation. (ie in case of * `ngIf`) (-1 otherwise) */ function i18nStartFirstCreatePass(tView, parentTNodeIndex, lView, index, message, subTemplateIndex) { const rootTNode = getCurrentParentTNode(); const createOpCodes = []; const updateOpCodes = []; const existingTNodeStack = [[]]; if (ngDevMode) { attachDebugGetter(createOpCodes, i18nCreateOpCodesToString); attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString); } message = getTranslationForTemplate(message, subTemplateIndex); const msgParts = replaceNgsp(message).split(PH_REGEXP); for (let i = 0; i < msgParts.length; i++) { let value = msgParts[i]; if ((i & 1) === 0) { // Even indexes are text (including bindings & ICU expressions) const parts = i18nParseTextIntoPartsAndICU(value); for (let j = 0; j < parts.length; j++) { let part = parts[j]; if ((j & 1) === 0) { // `j` is odd therefore `part` is string const text = part; ngDevMode && assertString(text, 'Parsed ICU part should be string'); if (text !== '') { i18nStartFirstCreatePassProcessTextNode(tView, rootTNode, existingTNodeStack[0], createOpCodes, updateOpCodes, lView, text); } } else { // `j` is Even therefor `part` is an `ICUExpression` const icuExpression = part; // Verify that ICU expression has the right shape. Translations might contain invalid // constructions (while original messages were correct), so ICU parsing at runtime may // not succeed (thus `icuExpression` remains a string). // Note: we intentionally retain the error here by not using `ngDevMode`, because // the value can change based on the locale and users aren't guaranteed to hit // an invalid string while they're developing. if (typeof icuExpression !== 'object') { throw new Error(`Unable to parse ICU expression in "${message}" message.`); } const icuContainerTNode = createTNodeAndAddOpCode(tView, rootTNode, existingTNodeStack[0], lView, createOpCodes, ngDevMode ? `ICU ${index}:${icuExpression.mainBinding}` : '', true); const icuNodeIndex = icuContainerTNode.index; ngDevMode && assertGreaterThanOrEqual(icuNodeIndex, HEADER_OFFSET, 'Index must be in absolute LView offset'); icuStart(tView, lView, updateOpCodes, parentTNodeIndex, icuExpression, icuNodeIndex); } } } else { // Odd indexes are placeholders (elements and sub-templates) // At this point value is something like: '/#1:2' (originally coming from '�/#1:2�') const isClosing = value.charCodeAt(0) === 47 /* CharCode.SLASH */; const type = value.charCodeAt(isClosing ? 1 : 0); ngDevMode && assertOneOf(type, 42 /* CharCode.STAR */, 35 /* CharCode.HASH */); const index = HEADER_OFFSET + Number.parseInt(value.substring((isClosing ? 2 : 1))); if (isClosing) { existingTNodeStack.shift(); setCurrentTNode(getCurrentParentTNode(), false); } else { const tNode = createTNodePlaceholder(tView, existingTNodeStack[0], index); existingTNodeStack.unshift([]); setCurrentTNode(tNode, true); } } } tView.data[index] = { create: createOpCodes, update: updateOpCodes, }; } /** * Allocate space in i18n Range add create OpCode instruction to create a text or comment node. * * @param tView Current `TView` needed to allocate space in i18n range. * @param rootTNode Root `TNode` of the i18n block. This node determines if the new TNode will be * added as part of the `i18nStart` instruction or as part of the `TNode.insertBeforeIndex`. * @param existingTNodes internal state for `addTNodeAndUpdateInsertBeforeIndex`. * @param lView Current `LView` needed to allocate space in i18n range. * @param createOpCodes Array storing `I18nCreateOpCodes` where new opCodes will be added. * @param text Text to be added when the `Text` or `Comment` node will be created. * @param isICU true if a `Comment` node for ICU (instead of `Text`) node should be created. */ function createTNodeAndAddOpCode(tView, rootTNode, existingTNodes, lView, createOpCodes, text, isICU) { const i18nNodeIdx = allocExpando(tView, lView, 1, null); let opCode = i18nNodeIdx << I18nCreateOpCode.SHIFT; let parentTNode = getCurrentParentTNode(); if (rootTNode === parentTNode) { // FIXME(misko): A null `parentTNode` should represent when we fall of the `LView` boundary. // (there is no parent), but in some circumstances (because we are inconsistent about how we set // `previousOrParentTNode`) it could point to `rootTNode` So this is a work around. parentTNode = null; } if (parentTNode === null) { // If we don't have a parent that means that we can eagerly add nodes. // If we have a parent than these nodes can't be added now (as the parent has not been created // yet) and instead the `parentTNode` is responsible for adding it. See // `TNode.insertBeforeIndex` opCode |= I18nCreateOpCode.APPEND_EAGERLY; } if (isICU) { opCode |= I18nCreateOpCode.COMMENT; ensureIcuContainerVisitorLoaded(loadIcuContainerVisitor); } createOpCodes.push(opCode, text === null ? '' : text); // We store `{{?}}` so that when looking at debug `TNodeType.template` we can see where the // bindings are. const tNode = createTNodeAtIndex(tView, i18nNodeIdx, isICU ? 32 /* TNodeType.Icu */ : 1 /* TNodeType.Text */, text === null ? (ngDevMode ? '{{?}}' : '') : text, null); addTNodeAndUpdateInsertBeforeIndex(existingTNodes, tNode); const tNodeIdx = tNode.index; setCurrentTNode(tNode, false /* Text nodes are self closing */); if (parentTNode !== null && rootTNode !== parentTNode) { // We are a child of deeper node (rather than a direct child of `i18nStart` instruction.) // We have to make sure to add ourselves to the parent. setTNodeInsertBeforeIndex(parentTNode, tNodeIdx); } return tNode; } /** * Processes text node in i18n block. * * Text nodes can have: * - Create instruction in `createOpCodes` for creating the text node. * - Allocate spec for text node in i18n range of `LView` * - If contains binding: * - bindings => allocate space in i18n range of `LView` to store the binding value. * - populate `updateOpCodes` with update instructions. * * @param tView Current `TView` * @param rootTNode Root `TNode` of the i18n block. This node determines if the new TNode will * be added as part of the `i18nStart` instruction or as part of the * `TNode.insertBeforeIndex`. * @param existingTNodes internal state for `addTNodeAndUpdateInsertBeforeIndex`. * @param createOpCodes Location where the creation OpCodes will be stored. * @param lView Current `LView` * @param text The translated text (which may contain binding) */ function i18nStartFirstCreatePassProcessTextNode(tView, rootTNode, existingTNodes, createOpCodes, updateOpCodes, lView, text) { const hasBinding = text.match(BINDING_REGEXP); const tNode = createTNodeAndAddOpCode(tView, rootTNode, existingTNodes, lView, createOpCodes, hasBinding ? null : text, false); if (hasBinding) { generateBindingUpdateOpCodes(updateOpCodes, text, tNode.index, null, 0, null); } } /** * See `i18nAttributes` above. */ function i18nAttributesFirstPass(tView, index, values) { const previousElement = getCurrentTNode(); const previousElementIndex = previousElement.index; const updateOpCodes = []; if (ngDevMode) { attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString); } if (tView.firstCreatePass && tView.data[index] === null) { for (let i = 0; i < values.length; i += 2) { const attrName = values[i]; const message = values[i + 1]; if (message !== '') { // Check if attribute value contains an ICU and throw an error if that's the case. // ICUs in element attributes are not supported. // Note: we intentionally retain the error here by not using `ngDevMode`, because // the `value` can change based on the locale and users aren't guaranteed to hit // an invalid string while they're developing. if (ICU_REGEXP.test(message)) { throw new Error(`ICU expressions are not supported in attributes. Message: "${message}".`); } // i18n attributes that hit this code path are guaranteed to have bindings, because // the compiler treats static i18n attributes as regular attribute bindings. // Since this may not be the first i18n attribute on this element we need to pass in how // many previous bindings there have already been. generateBindingUpdateOpCodes(updateOpCodes, message, previousElementIndex, attrName, countBindings(updateOpCodes), null); } } tView.data[index] = updateOpCodes; } } /** * Generate the OpCodes to update the bindings of a string. * * @param updateOpCodes Place where the update opcodes will be stored. * @param str The string containing the bindings. * @param destinationNode Index of the destination node which will receive the binding. * @param attrName Name of the attribute, if the string belongs to an attribute. * @param sanitizeFn Sanitization function used to sanitize the string after update, if necessary. * @param bindingStart The lView index of the next expression that can be bound via an opCode. * @returns The mask value for these bindings */ function generateBindingUpdateOpCodes(updateOpCodes, str, destinationNode, attrName, bindingStart, sanitizeFn) { ngDevMode && assertGreaterThanOrEqual(destinationNode, HEADER_OFFSET, 'Index must be in absolute LView offset'); const maskIndex = updateOpCodes.length; // Location of mask const sizeIndex = maskIndex + 1; // location of size for skipping updateOpCodes.push(null, null); // Alloc space for mask and size const startIndex = maskIndex + 2; // location of first allocation. if (ngDevMode) { attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString); } const textParts = str.split(BINDING_REGEXP); let mask = 0; for (let j = 0; j < textParts.length; j++) { const textValue = textParts[j]; if (j & 1) { // Odd indexes are bindings const bindingIndex = bindingStart + parseInt(textValue, 10); updateOpCodes.push(-1 - bindingIndex); mask = mask | toMaskBit(bindingIndex); } else if (textValue !== '') { // Even indexes are text updateOpCodes.push(textValue); } } updateOpCodes.push(destinationNode << 2 /* I18nUpdateOpCode.SHIFT_REF */ | (attrName ? 1 /* I18nUpdateOpCode.Attr */ : 0 /* I18nUpdateOpCode.Text */)); if (attrName) { updateOpCodes.push(attrName, sanitizeFn); } updateOpCodes[maskIndex] = mask; updateOpCodes[sizeIndex] = updateOpCodes.length - startIndex; return mask; } /** * Count the number of bindings in the given `opCodes`. * * It could be possible to speed this up, by passing the number of bindings found back from * `generateBindingUpdateOpCodes()` to `i18nAttributesFirstPass()` but this would then require more * complexity in the code and/or transient objects to be created. * * Since this function is only called once when the template is instantiated, is trivial in the * first instance (since `opCodes` will be an empty array), and it is not common for elements to * contain multiple i18n bound attributes, it seems like this is a reasonable compromise. */ function countBindings(opCodes) { let count = 0; for (let i = 0; i < opCodes.length; i++) { const opCode = opCodes[i]; // Bindings are negative numbers. if (typeof opCode === 'number' && opCode < 0) { count++; } } return count; } /** * Convert binding index to mask bit. * * Each index represents a single bit on the bit-mask. Because bit-mask only has 32 bits, we make * the 32nd bit share all masks for all bindings higher than 32. Since it is extremely rare to * have more than 32 bindings this will be hit very rarely. The downside of hitting this corner * case is that we will execute binding code more often than necessary. (penalty of performance) */ function toMaskBit(bindingIndex) { return 1 << Math.min(bindingIndex, 31); } function isRootTemplateMessage(subTemplateIndex) { return subTemplateIndex === -1; } /** * Removes everything inside the sub-templates of a message. */ function removeInnerTemplateTranslation(message) { let match; let res = ''; let index = 0; let inTemplate = false; let tagMatched; while ((match = SUBTEMPLATE_REGEXP.exec(message)) !== null) { if (!inTemplate) { res += message.substring(index, match.index + match[0].length); tagMatched = match[1]; inTemplate = true; } else { if (match[0] === `${MARKER}/*${tagMatched}${MARKER}`) { index = match.index; inTemplate = false; } } } ngDevMode && assertEqual(inTemplate, false, `Tag mismatch: unable to find the end of the sub-template in the translation "${message}"`); res += message.slice(index); return res; } /** * Extracts a part of a message and removes the rest. * * This method is used for extracting a part of the message associated with a template. A * translated message can span multiple templates. * * Example: * ``` *
Translate me!
* ``` * * @param message The message to crop * @param subTemplateIndex Index of the sub-template to extract. If undefined it returns the * external template and removes all sub-templates. */ function getTranslationForTemplate(message, subTemplateIndex) { if (isRootTemplateMessage(subTemplateIndex)) { // We want the root template message, ignore all sub-templates return removeInnerTemplateTranslation(message); } else { // We want a specific sub-template const start = message.indexOf(`:${subTemplateIndex}${MARKER}`) + 2 + subTemplateIndex.toString().length; const end = message.search(new RegExp(`${MARKER}\\/\\*\\d+:${subTemplateIndex}${MARKER}`)); return removeInnerTemplateTranslation(message.substring(start, end)); } } /** * Generate the OpCodes for ICU expressions. * * @param icuExpression * @param index Index where the anchor is stored and an optional `TIcuContainerNode` * - `lView[anchorIdx]` points to a `Comment` node representing the anchor for the ICU. * - `tView.data[anchorIdx]` points to the `TIcuContainerNode` if ICU is root (`null` otherwise) */ function icuStart(tView, lView, updateOpCodes, parentIdx, icuExpression, anchorIdx) { ngDevMode && assertDefined(icuExpression, 'ICU expression must be defined'); let bindingMask = 0; const tIcu = { type: icuExpression.type, currentCaseLViewIndex: allocExpando(tView, lView, 1, null), anchorIdx, cases: [], create: [], remove: [], update: [] }; addUpdateIcuSwitch(updateOpCodes, icuExpression, anchorIdx); setTIcu(tView, anchorIdx, tIcu); const values = icuExpression.values; for (let i = 0; i < values.length; i++) { // Each value is an array of strings & other ICU expressions const valueArr = values[i]; const nestedIcus = []; for (let j = 0; j < valueArr.length; j++) { const value = valueArr[j]; if (typeof value !== 'string') { // It is an nested ICU expression const icuIndex = nestedIcus.push(value) - 1; // Replace nested ICU expression by a comment node valueArr[j] = ``; } } bindingMask = parseIcuCase(tView, tIcu, lView, updateOpCodes, parentIdx, icuExpression.cases[i], valueArr.join(''), nestedIcus) | bindingMask; } if (bindingMask) { addUpdateIcuUpdate(updateOpCodes, bindingMask, anchorIdx); } } /** * Parses text containing an ICU expression and produces a JSON object for it. * Original code from closure library, modified for Angular. * * @param pattern Text containing an ICU expression that needs to be parsed. * */ function parseICUBlock(pattern) { const cases = []; const values = []; let icuType = 1 /* IcuType.plural */; let mainBinding = 0; pattern = pattern.replace(ICU_BLOCK_REGEXP, function (str, binding, type) { if (type === 'select') { icuType = 0 /* IcuType.select */; } else { icuType = 1 /* IcuType.plural */; } mainBinding = parseInt(binding.slice(1), 10); return ''; }); const parts = i18nParseTextIntoPartsAndICU(pattern); // Looking for (key block)+ sequence. One of the keys has to be "other". for (let pos = 0; pos < parts.length;) { let key = parts[pos++].trim(); if (icuType === 1 /* IcuType.plural */) { // Key can be "=x", we just want "x" key = key.replace(/\s*(?:=)?(\w+)\s*/, '$1'); } if (key.length) { cases.push(key); } const blocks = i18nParseTextIntoPartsAndICU(parts[pos++]); if (cases.length > values.length) { values.push(blocks); } } // TODO(ocombe): support ICU expressions in attributes, see #21615 return { type: icuType, mainBinding: mainBinding, cases, values }; } /** * Breaks pattern into strings and top level {...} blocks. * Can be used to break a message into text and ICU expressions, or to break an ICU expression * into keys and cases. Original code from closure library, modified for Angular. * * @param pattern (sub)Pattern to be broken. * @returns An `Array` where: * - odd positions: `string` => text between ICU expressions * - even positions: `ICUExpression` => ICU expression parsed into `ICUExpression` record. */ function i18nParseTextIntoPartsAndICU(pattern) { if (!pattern) { return []; } let prevPos = 0; const braceStack = []; const results = []; const braces = /[{}]/g; // lastIndex doesn't get set to 0 so we have to. braces.lastIndex = 0; let match; while (match = braces.exec(pattern)) { const pos = match.index; if (match[0] == '}') { braceStack.pop(); if (braceStack.length == 0) { // End of the block. const block = pattern.substring(prevPos, pos); if (ICU_BLOCK_REGEXP.test(block)) { results.push(parseICUBlock(block)); } else { results.push(block); } prevPos = pos + 1; } } else { if (braceStack.length == 0) { const substring = pattern.substring(prevPos, pos); results.push(substring); prevPos = pos + 1; } braceStack.push('{'); } } const substring = pattern.substring(prevPos); results.push(substring); return results; } /** * Parses a node, its children and its siblings, and generates the mutate & update OpCodes. * */ function parseIcuCase(tView, tIcu, lView, updateOpCodes, parentIdx, caseName, unsafeCaseHtml, nestedIcus) { const create = []; const remove = []; const update = []; if (ngDevMode) { attachDebugGetter(create, icuCreateOpCodesToString); attachDebugGetter(remove, i18nRemoveOpCodesToString); attachDebugGetter(update, i18nUpdateOpCodesToString); } tIcu.cases.push(caseName); tIcu.create.push(create); tIcu.remove.push(remove); tIcu.update.push(update); const inertBodyHelper = getInertBodyHelper(getDocument()); const inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeCaseHtml); ngDevMode && assertDefined(inertBodyElement, 'Unable to generate inert body element'); const inertRootNode = getTemplateContent(inertBodyElement) || inertBodyElement; if (inertRootNode) { return walkIcuTree(tView, tIcu, lView, updateOpCodes, create, remove, update, inertRootNode, parentIdx, nestedIcus, 0); } else { return 0; } } function walkIcuTree(tView, tIcu, lView, sharedUpdateOpCodes, create, remove, update, parentNode, parentIdx, nestedIcus, depth) { let bindingMask = 0; let currentNode = parentNode.firstChild; while (currentNode) { const newIndex = allocExpando(tView, lView, 1, null); switch (currentNode.nodeType) { case Node.ELEMENT_NODE: const element = currentNode; const tagName = element.tagName.toLowerCase(); if (VALID_ELEMENTS.hasOwnProperty(tagName)) { addCreateNodeAndAppend(create, ELEMENT_MARKER, tagName, parentIdx, newIndex); tView.data[newIndex] = tagName; const elAttrs = element.attributes; for (let i = 0; i < elAttrs.length; i++) { const attr = elAttrs.item(i); const lowerAttrName = attr.name.toLowerCase(); const hasBinding = !!attr.value.match(BINDING_REGEXP); // we assume the input string is safe, unless it's using a binding if (hasBinding) { if (VALID_ATTRS.hasOwnProperty(lowerAttrName)) { if (URI_ATTRS[lowerAttrName]) { generateBindingUpdateOpCodes(update, attr.value, newIndex, attr.name, 0, _sanitizeUrl); } else { generateBindingUpdateOpCodes(update, attr.value, newIndex, attr.name, 0, null); } } else { ngDevMode && console.warn(`WARNING: ignoring unsafe attribute value ` + `${lowerAttrName} on element ${tagName} ` + `(see ${XSS_SECURITY_URL})`); } } else { addCreateAttribute(create, newIndex, attr); } } // Parse the children of this node (if any) bindingMask = walkIcuTree(tView, tIcu, lView, sharedUpdateOpCodes, create, remove, update, currentNode, newIndex, nestedIcus, depth + 1) | bindingMask; addRemoveNode(remove, newIndex, depth); } break; case Node.TEXT_NODE: const value = currentNode.textContent || ''; const hasBinding = value.match(BINDING_REGEXP); addCreateNodeAndAppend(create, null, hasBinding ? '' : value, parentIdx, newIndex); addRemoveNode(remove, newIndex, depth); if (hasBinding) { bindingMask = generateBindingUpdateOpCodes(update, value, newIndex, null, 0, null) | bindingMask; } break; case Node.COMMENT_NODE: // Check if the comment node is a placeholder for a nested ICU const isNestedIcu = NESTED_ICU.exec(currentNode.textContent || ''); if (isNestedIcu) { const nestedIcuIndex = parseInt(isNestedIcu[1], 10); const icuExpression = nestedIcus[nestedIcuIndex]; // Create the comment node that will anchor the ICU expression addCreateNodeAndAppend(create, ICU_MARKER, ngDevMode ? `nested ICU ${nestedIcuIndex}` : '', parentIdx, newIndex); icuStart(tView, lView, sharedUpdateOpCodes, parentIdx, icuExpression, newIndex); addRemoveNestedIcu(remove, newIndex, depth); } break; } currentNode = currentNode.nextSibling; } return bindingMask; } function addRemoveNode(remove, index, depth) { if (depth === 0) { remove.push(index); } } function addRemoveNestedIcu(remove, index, depth) { if (depth === 0) { remove.push(~index); // remove ICU at `index` remove.push(index); // remove ICU comment at `index` } } function addUpdateIcuSwitch(update, icuExpression, index) { update.push(toMaskBit(icuExpression.mainBinding), 2, -1 - icuExpression.mainBinding, index << 2 /* I18nUpdateOpCode.SHIFT_REF */ | 2 /* I18nUpdateOpCode.IcuSwitch */); } function addUpdateIcuUpdate(update, bindingMask, index) { update.push(bindingMask, 1, index << 2 /* I18nUpdateOpCode.SHIFT_REF */ | 3 /* I18nUpdateOpCode.IcuUpdate */); } function addCreateNodeAndAppend(create, marker, text, appendToParentIdx, createAtIdx) { if (marker !== null) { create.push(marker); } create.push(text, createAtIdx, icuCreateOpCode(0 /* IcuCreateOpCode.AppendChild */, appendToParentIdx, createAtIdx)); } function addCreateAttribute(create, newIndex, attr) { create.push(newIndex << 1 /* IcuCreateOpCode.SHIFT_REF */ | 1 /* IcuCreateOpCode.Attr */, attr.name, attr.value); } // i18nPostprocess consts const ROOT_TEMPLATE_ID = 0; const PP_MULTI_VALUE_PLACEHOLDERS_REGEXP = /\[(�.+?�?)\]/; const PP_PLACEHOLDERS_REGEXP = /\[(�.+?�?)\]|(�\/?\*\d+:\d+�)/g; const PP_ICU_VARS_REGEXP = /({\s*)(VAR_(PLURAL|SELECT)(_\d+)?)(\s*,)/g; const PP_ICU_PLACEHOLDERS_REGEXP = /{([A-Z0-9_]+)}/g; const PP_ICUS_REGEXP = /�I18N_EXP_(ICU(_\d+)?)�/g; const PP_CLOSE_TEMPLATE_REGEXP = /\/\*/; const PP_TEMPLATE_ID_REGEXP = /\d+\:(\d+)/; /** * Handles message string post-processing for internationalization. * * Handles message string post-processing by transforming it from intermediate * format (that might contain some markers that we need to replace) to the final * form, consumable by i18nStart instruction. Post processing steps include: * * 1. Resolve all multi-value cases (like [�*1:1��#2:1�|�#4:1�|�5�]) * 2. Replace all ICU vars (like "VAR_PLURAL") * 3. Replace all placeholders used inside ICUs in a form of {PLACEHOLDER} * 4. Replace all ICU references with corresponding values (like �ICU_EXP_ICU_1�) * in case multiple ICUs have the same placeholder name * * @param message Raw translation string for post processing * @param replacements Set of replacements that should be applied * * @returns Transformed string that can be consumed by i18nStart instruction * * @codeGenApi */ function i18nPostprocess(message, replacements = {}) { /** * Step 1: resolve all multi-value placeholders like [�#5�|�*1:1��#2:1�|�#4:1�] * * Note: due to the way we process nested templates (BFS), multi-value placeholders are typically * grouped by templates, for example: [�#5�|�#6�|�#1:1�|�#3:2�] where �#5� and �#6� belong to root * template, �#1:1� belong to nested template with index 1 and �#1:2� - nested template with index * 3. However in real templates the order might be different: i.e. �#1:1� and/or �#3:2� may go in * front of �#6�. The post processing step restores the right order by keeping track of the * template id stack and looks for placeholders that belong to the currently active template. */ let result = message; if (PP_MULTI_VALUE_PLACEHOLDERS_REGEXP.test(message)) { const matches = {}; const templateIdsStack = [ROOT_TEMPLATE_ID]; result = result.replace(PP_PLACEHOLDERS_REGEXP, (m, phs, tmpl) => { const content = phs || tmpl; const placeholders = matches[content] || []; if (!placeholders.length) { content.split('|').forEach((placeholder) => { const match = placeholder.match(PP_TEMPLATE_ID_REGEXP); const templateId = match ? parseInt(match[1], 10) : ROOT_TEMPLATE_ID; const isCloseTemplateTag = PP_CLOSE_TEMPLATE_REGEXP.test(placeholder); placeholders.push([templateId, isCloseTemplateTag, placeholder]); }); matches[content] = placeholders; } if (!placeholders.length) { throw new Error(`i18n postprocess: unmatched placeholder - ${content}`); } const currentTemplateId = templateIdsStack[templateIdsStack.length - 1]; let idx = 0; // find placeholder index that matches current template id for (let i = 0; i < placeholders.length; i++) { if (placeholders[i][0] === currentTemplateId) { idx = i; break; } } // update template id stack based on the current tag extracted const [templateId, isCloseTemplateTag, placeholder] = placeholders[idx]; if (isCloseTemplateTag) { templateIdsStack.pop(); } else if (currentTemplateId !== templateId) { templateIdsStack.push(templateId); } // remove processed tag from the list placeholders.splice(idx, 1); return placeholder; }); } // return current result if no replacements specified if (!Object.keys(replacements).length) { return result; } /** * Step 2: replace all ICU vars (like "VAR_PLURAL") */ result = result.replace(PP_ICU_VARS_REGEXP, (match, start, key, _type, _idx, end) => { return replacements.hasOwnProperty(key) ? `${start}${replacements[key]}${end}` : match; }); /** * Step 3: replace all placeholders used inside ICUs in a form of {PLACEHOLDER} */ result = result.replace(PP_ICU_PLACEHOLDERS_REGEXP, (match, key) => { return replacements.hasOwnProperty(key) ? replacements[key] : match; }); /** * Step 4: replace all ICU references with corresponding values (like �ICU_EXP_ICU_1�) in case * multiple ICUs have the same placeholder name */ result = result.replace(PP_ICUS_REGEXP, (match, key) => { if (replacements.hasOwnProperty(key)) { const list = replacements[key]; if (!list.length) { throw new Error(`i18n postprocess: unmatched ICU - ${match} with key: ${key}`); } return list.shift(); } return match; }); return result; } /** * Marks a block of text as translatable. * * The instructions `i18nStart` and `i18nEnd` mark the translation block in the template. * The translation `message` is the value which is locale specific. The translation string may * contain placeholders which associate inner elements and sub-templates within the translation. * * The translation `message` placeholders are: * - `�{index}(:{block})�`: *Binding Placeholder*: Marks a location where an expression will be * interpolated into. The placeholder `index` points to the expression binding index. An optional * `block` that matches the sub-template in which it was declared. * - `�#{index}(:{block})�`/`�/#{index}(:{block})�`: *Element Placeholder*: Marks the beginning * and end of DOM element that were embedded in the original translation block. The placeholder * `index` points to the element index in the template instructions set. An optional `block` that * matches the sub-template in which it was declared. * - `�*{index}:{block}�`/`�/*{index}:{block}�`: *Sub-template Placeholder*: Sub-templates must be * split up and translated separately in each angular template function. The `index` points to the * `template` instruction index. A `block` that matches the sub-template in which it was declared. * * @param index A unique index of the translation in the static block. * @param messageIndex An index of the translation message from the `def.consts` array. * @param subTemplateIndex Optional sub-template index in the `message`. * * @codeGenApi */ function ɵɵi18nStart(index, messageIndex, subTemplateIndex = -1) { const tView = getTView(); const lView = getLView(); const adjustedIndex = HEADER_OFFSET + index; ngDevMode && assertDefined(tView, `tView should be defined`); const message = getConstant(tView.consts, messageIndex); const parentTNode = getCurrentParentTNode(); if (tView.firstCreatePass) { i18nStartFirstCreatePass(tView, parentTNode === null ? 0 : parentTNode.index, lView, adjustedIndex, message, subTemplateIndex); } // Set a flag that this LView has i18n blocks. // The flag is later used to determine whether this component should // be hydrated (currently hydration is not supported for i18n blocks). if (tView.type === 2 /* TViewType.Embedded */) { // Annotate host component's LView (not embedded view's LView), // since hydration can be skipped on per-component basis only. const componentLView = lView[DECLARATION_COMPONENT_VIEW]; componentLView[FLAGS] |= 32 /* LViewFlags.HasI18n */; } else { lView[FLAGS] |= 32 /* LViewFlags.HasI18n */; } const tI18n = tView.data[adjustedIndex]; const sameViewParentTNode = parentTNode === lView[T_HOST] ? null : parentTNode; const parentRNode = getClosestRElement(tView, sameViewParentTNode, lView); // If `parentTNode` is an `ElementContainer` than it has ``. // When we do inserts we have to make sure to insert in front of ``. const insertInFrontOf = parentTNode && (parentTNode.type & 8 /* TNodeType.ElementContainer */) ? lView[parentTNode.index] : null; applyCreateOpCodes(lView, tI18n.create, parentRNode, insertInFrontOf); setInI18nBlock(true); } /** * Translates a translation block marked by `i18nStart` and `i18nEnd`. It inserts the text/ICU nodes * into the render tree, moves the placeholder nodes and removes the deleted nodes. * * @codeGenApi */ function ɵɵi18nEnd() { setInI18nBlock(false); } /** * * Use this instruction to create a translation block that doesn't contain any placeholder. * It calls both {@link i18nStart} and {@link i18nEnd} in one instruction. * * The translation `message` is the value which is locale specific. The translation string may * contain placeholders which associate inner elements and sub-templates within the translation. * * The translation `message` placeholders are: * - `�{index}(:{block})�`: *Binding Placeholder*: Marks a location where an expression will be * interpolated into. The placeholder `index` points to the expression binding index. An optional * `block` that matches the sub-template in which it was declared. * - `�#{index}(:{block})�`/`�/#{index}(:{block})�`: *Element Placeholder*: Marks the beginning * and end of DOM element that were embedded in the original translation block. The placeholder * `index` points to the element index in the template instructions set. An optional `block` that * matches the sub-template in which it was declared. * - `�*{index}:{block}�`/`�/*{index}:{block}�`: *Sub-template Placeholder*: Sub-templates must be * split up and translated separately in each angular template function. The `index` points to the * `template` instruction index. A `block` that matches the sub-template in which it was declared. * * @param index A unique index of the translation in the static block. * @param messageIndex An index of the translation message from the `def.consts` array. * @param subTemplateIndex Optional sub-template index in the `message`. * * @codeGenApi */ function ɵɵi18n(index, messageIndex, subTemplateIndex) { ɵɵi18nStart(index, messageIndex, subTemplateIndex); ɵɵi18nEnd(); } /** * Marks a list of attributes as translatable. * * @param index A unique index in the static block * @param values * * @codeGenApi */ function ɵɵi18nAttributes(index, attrsIndex) { const tView = getTView(); ngDevMode && assertDefined(tView, `tView should be defined`); const attrs = getConstant(tView.consts, attrsIndex); i18nAttributesFirstPass(tView, index + HEADER_OFFSET, attrs); } /** * Stores the values of the bindings during each update cycle in order to determine if we need to * update the translated nodes. * * @param value The binding's value * @returns This function returns itself so that it may be chained * (e.g. `i18nExp(ctx.name)(ctx.title)`) * * @codeGenApi */ function ɵɵi18nExp(value) { const lView = getLView(); setMaskBit(bindingUpdated(lView, nextBindingIndex(), value)); return ɵɵi18nExp; } /** * Updates a translation block or an i18n attribute when the bindings have changed. * * @param index Index of either {@link i18nStart} (translation block) or {@link i18nAttributes} * (i18n attribute) on which it should update the content. * * @codeGenApi */ function ɵɵi18nApply(index) { applyI18n(getTView(), getLView(), index + HEADER_OFFSET); } /** * Handles message string post-processing for internationalization. * * Handles message string post-processing by transforming it from intermediate * format (that might contain some markers that we need to replace) to the final * form, consumable by i18nStart instruction. Post processing steps include: * * 1. Resolve all multi-value cases (like [�*1:1��#2:1�|�#4:1�|�5�]) * 2. Replace all ICU vars (like "VAR_PLURAL") * 3. Replace all placeholders used inside ICUs in a form of {PLACEHOLDER} * 4. Replace all ICU references with corresponding values (like �ICU_EXP_ICU_1�) * in case multiple ICUs have the same placeholder name * * @param message Raw translation string for post processing * @param replacements Set of replacements that should be applied * * @returns Transformed string that can be consumed by i18nStart instruction * * @codeGenApi */ function ɵɵi18nPostprocess(message, replacements = {}) { return i18nPostprocess(message, replacements); } /* * This file re-exports all symbols contained in this directory. * * Why is this file not `index.ts`? * * There seems to be an inconsistent path resolution of an `index.ts` file * when only the parent directory is referenced. This could be due to the * node module resolution configuration differing from rollup and/or typescript. * * With commit * https://github.com/angular/angular/commit/d5e3f2c64bd13ce83e7c70788b7fc514ca4a9918 * the `instructions.ts` file was moved to `instructions/instructions.ts` and an * `index.ts` file was used to re-export everything. Having had file names that were * importing from `instructions' directly (not the from the sub file or the `index.ts` * file) caused strange CI issues. `index.ts` had to be renamed to `all.ts` for this * to work. * * Jira Issue = FW-1184 */ /** * Resolves the providers which are defined in the DirectiveDef. * * When inserting the tokens and the factories in their respective arrays, we can assume that * this method is called first for the component (if any), and then for other directives on the same * node. * As a consequence,the providers are always processed in that order: * 1) The view providers of the component * 2) The providers of the component * 3) The providers of the other directives * This matches the structure of the injectables arrays of a view (for each node). * So the tokens and the factories can be pushed at the end of the arrays, except * in one case for multi providers. * * @param def the directive definition * @param providers: Array of `providers`. * @param viewProviders: Array of `viewProviders`. */ function providersResolver(def, providers, viewProviders) { const tView = getTView(); if (tView.firstCreatePass) { const isComponent = isComponentDef(def); // The list of view providers is processed first, and the flags are updated resolveProvider(viewProviders, tView.data, tView.blueprint, isComponent, true); // Then, the list of providers is processed, and the flags are updated resolveProvider(providers, tView.data, tView.blueprint, isComponent, false); } } /** * Resolves a provider and publishes it to the DI system. */ function resolveProvider(provider, tInjectables, lInjectablesBlueprint, isComponent, isViewProvider) { provider = resolveForwardRef(provider); if (Array.isArray(provider)) { // Recursively call `resolveProvider` // Recursion is OK in this case because this code will not be in hot-path once we implement // cloning of the initial state. for (let i = 0; i < provider.length; i++) { resolveProvider(provider[i], tInjectables, lInjectablesBlueprint, isComponent, isViewProvider); } } else { const tView = getTView(); const lView = getLView(); let token = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide); let providerFactory = providerToFactory(provider); const tNode = getCurrentTNode(); const beginIndex = tNode.providerIndexes & 1048575 /* TNodeProviderIndexes.ProvidersStartIndexMask */; const endIndex = tNode.directiveStart; const cptViewProvidersCount = tNode.providerIndexes >> 20 /* TNodeProviderIndexes.CptViewProvidersCountShift */; if (isTypeProvider(provider) || !provider.multi) { // Single provider case: the factory is created and pushed immediately const factory = new NodeInjectorFactory(providerFactory, isViewProvider, ɵɵdirectiveInject); const existingFactoryIndex = indexOf(token, tInjectables, isViewProvider ? beginIndex : beginIndex + cptViewProvidersCount, endIndex); if (existingFactoryIndex === -1) { diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, lView), tView, token); registerDestroyHooksIfSupported(tView, provider, tInjectables.length); tInjectables.push(token); tNode.directiveStart++; tNode.directiveEnd++; if (isViewProvider) { tNode.providerIndexes += 1048576 /* TNodeProviderIndexes.CptViewProvidersCountShifter */; } lInjectablesBlueprint.push(factory); lView.push(factory); } else { lInjectablesBlueprint[existingFactoryIndex] = factory; lView[existingFactoryIndex] = factory; } } else { // Multi provider case: // We create a multi factory which is going to aggregate all the values. // Since the output of such a factory depends on content or view injection, // we create two of them, which are linked together. // // The first one (for view providers) is always in the first block of the injectables array, // and the second one (for providers) is always in the second block. // This is important because view providers have higher priority. When a multi token // is being looked up, the view providers should be found first. // Note that it is not possible to have a multi factory in the third block (directive block). // // The algorithm to process multi providers is as follows: // 1) If the multi provider comes from the `viewProviders` of the component: // a) If the special view providers factory doesn't exist, it is created and pushed. // b) Else, the multi provider is added to the existing multi factory. // 2) If the multi provider comes from the `providers` of the component or of another // directive: // a) If the multi factory doesn't exist, it is created and provider pushed into it. // It is also linked to the multi factory for view providers, if it exists. // b) Else, the multi provider is added to the existing multi factory. const existingProvidersFactoryIndex = indexOf(token, tInjectables, beginIndex + cptViewProvidersCount, endIndex); const existingViewProvidersFactoryIndex = indexOf(token, tInjectables, beginIndex, beginIndex + cptViewProvidersCount); const doesProvidersFactoryExist = existingProvidersFactoryIndex >= 0 && lInjectablesBlueprint[existingProvidersFactoryIndex]; const doesViewProvidersFactoryExist = existingViewProvidersFactoryIndex >= 0 && lInjectablesBlueprint[existingViewProvidersFactoryIndex]; if (isViewProvider && !doesViewProvidersFactoryExist || !isViewProvider && !doesProvidersFactoryExist) { // Cases 1.a and 2.a diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, lView), tView, token); const factory = multiFactory(isViewProvider ? multiViewProvidersFactoryResolver : multiProvidersFactoryResolver, lInjectablesBlueprint.length, isViewProvider, isComponent, providerFactory); if (!isViewProvider && doesViewProvidersFactoryExist) { lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory; } registerDestroyHooksIfSupported(tView, provider, tInjectables.length, 0); tInjectables.push(token); tNode.directiveStart++; tNode.directiveEnd++; if (isViewProvider) { tNode.providerIndexes += 1048576 /* TNodeProviderIndexes.CptViewProvidersCountShifter */; } lInjectablesBlueprint.push(factory); lView.push(factory); } else { // Cases 1.b and 2.b const indexInFactory = multiFactoryAdd(lInjectablesBlueprint[isViewProvider ? existingViewProvidersFactoryIndex : existingProvidersFactoryIndex], providerFactory, !isViewProvider && isComponent); registerDestroyHooksIfSupported(tView, provider, existingProvidersFactoryIndex > -1 ? existingProvidersFactoryIndex : existingViewProvidersFactoryIndex, indexInFactory); } if (!isViewProvider && isComponent && doesViewProvidersFactoryExist) { lInjectablesBlueprint[existingViewProvidersFactoryIndex].componentProviders++; } } } } /** * Registers the `ngOnDestroy` hook of a provider, if the provider supports destroy hooks. * @param tView `TView` in which to register the hook. * @param provider Provider whose hook should be registered. * @param contextIndex Index under which to find the context for the hook when it's being invoked. * @param indexInFactory Only required for `multi` providers. Index of the provider in the multi * provider factory. */ function registerDestroyHooksIfSupported(tView, provider, contextIndex, indexInFactory) { const providerIsTypeProvider = isTypeProvider(provider); const providerIsClassProvider = isClassProvider(provider); if (providerIsTypeProvider || providerIsClassProvider) { // Resolve forward references as `useClass` can hold a forward reference. const classToken = providerIsClassProvider ? resolveForwardRef(provider.useClass) : provider; const prototype = classToken.prototype; const ngOnDestroy = prototype.ngOnDestroy; if (ngOnDestroy) { const hooks = tView.destroyHooks || (tView.destroyHooks = []); if (!providerIsTypeProvider && provider.multi) { ngDevMode && assertDefined(indexInFactory, 'indexInFactory when registering multi factory destroy hook'); const existingCallbacksIndex = hooks.indexOf(contextIndex); if (existingCallbacksIndex === -1) { hooks.push(contextIndex, [indexInFactory, ngOnDestroy]); } else { hooks[existingCallbacksIndex + 1].push(indexInFactory, ngOnDestroy); } } else { hooks.push(contextIndex, ngOnDestroy); } } } } /** * Add a factory in a multi factory. * @returns Index at which the factory was inserted. */ function multiFactoryAdd(multiFactory, factory, isComponentProvider) { if (isComponentProvider) { multiFactory.componentProviders++; } return multiFactory.multi.push(factory) - 1; } /** * Returns the index of item in the array, but only in the begin to end range. */ function indexOf(item, arr, begin, end) { for (let i = begin; i < end; i++) { if (arr[i] === item) return i; } return -1; } /** * Use this with `multi` `providers`. */ function multiProvidersFactoryResolver(_, tData, lData, tNode) { return multiResolve(this.multi, []); } /** * Use this with `multi` `viewProviders`. * * This factory knows how to concatenate itself with the existing `multi` `providers`. */ function multiViewProvidersFactoryResolver(_, tData, lView, tNode) { const factories = this.multi; let result; if (this.providerFactory) { const componentCount = this.providerFactory.componentProviders; const multiProviders = getNodeInjectable(lView, lView[TVIEW], this.providerFactory.index, tNode); // Copy the section of the array which contains `multi` `providers` from the component result = multiProviders.slice(0, componentCount); // Insert the `viewProvider` instances. multiResolve(factories, result); // Copy the section of the array which contains `multi` `providers` from other directives for (let i = componentCount; i < multiProviders.length; i++) { result.push(multiProviders[i]); } } else { result = []; // Insert the `viewProvider` instances. multiResolve(factories, result); } return result; } /** * Maps an array of factories into an array of values. */ function multiResolve(factories, result) { for (let i = 0; i < factories.length; i++) { const factory = factories[i]; result.push(factory()); } return result; } /** * Creates a multi factory. */ function multiFactory(factoryFn, index, isViewProvider, isComponent, f) { const factory = new NodeInjectorFactory(factoryFn, isViewProvider, ɵɵdirectiveInject); factory.multi = []; factory.index = index; factory.componentProviders = 0; multiFactoryAdd(factory, f, isComponent && !isViewProvider); return factory; } /** * This feature resolves the providers of a directive (or component), * and publish them into the DI system, making it visible to others for injection. * * For example: * ```ts * class ComponentWithProviders { * constructor(private greeter: GreeterDE) {} * * static ɵcmp = defineComponent({ * type: ComponentWithProviders, * selectors: [['component-with-providers']], * factory: () => new ComponentWithProviders(directiveInject(GreeterDE as any)), * decls: 1, * vars: 1, * template: function(fs: RenderFlags, ctx: ComponentWithProviders) { * if (fs & RenderFlags.Create) { * ɵɵtext(0); * } * if (fs & RenderFlags.Update) { * ɵɵtextInterpolate(ctx.greeter.greet()); * } * }, * features: [ɵɵProvidersFeature([GreeterDE])] * }); * } * ``` * * @param definition * * @codeGenApi */ function ɵɵProvidersFeature(providers, viewProviders = []) { return (definition) => { definition.providersResolver = (def, processProvidersFn) => { return providersResolver(def, // processProvidersFn ? processProvidersFn(providers) : providers, // viewProviders); }; }; } /** * Represents an instance of an `NgModule` created by an `NgModuleFactory`. * Provides access to the `NgModule` instance and related objects. * * @publicApi */ class NgModuleRef$1 { } exports('NgModuleRef', NgModuleRef$1); /** * @publicApi * * @deprecated * This class was mostly used as a part of ViewEngine-based JIT API and is no longer needed in Ivy * JIT mode. See [JIT API changes due to ViewEngine deprecation](guide/deprecations#jit-api-changes) * for additional context. Angular provides APIs that accept NgModule classes directly (such as * [PlatformRef.bootstrapModule](api/core/PlatformRef#bootstrapModule) and * [createNgModule](api/core/createNgModule)), consider switching to those APIs instead of * using factory-based ones. */ class NgModuleFactory$1 { } exports('NgModuleFactory', NgModuleFactory$1); /** * Returns a new NgModuleRef instance based on the NgModule class and parent injector provided. * * @param ngModule NgModule class. * @param parentInjector Optional injector instance to use as a parent for the module injector. If * not provided, `NullInjector` will be used instead. * @returns NgModuleRef that represents an NgModule instance. * * @publicApi */ function createNgModule(ngModule, parentInjector) { return new NgModuleRef(ngModule, parentInjector ?? null, []); } /** * The `createNgModule` function alias for backwards-compatibility. * Please avoid using it directly and use `createNgModule` instead. * * @deprecated Use `createNgModule` instead. */ const createNgModuleRef = exports('createNgModuleRef', createNgModule); class NgModuleRef extends NgModuleRef$1 { constructor(ngModuleType, _parent, additionalProviders) { super(); this._parent = _parent; // tslint:disable-next-line:require-internal-with-underscore this._bootstrapComponents = []; this.destroyCbs = []; // When bootstrapping a module we have a dependency graph that looks like this: // ApplicationRef -> ComponentFactoryResolver -> NgModuleRef. The problem is that if the // module being resolved tries to inject the ComponentFactoryResolver, it'll create a // circular dependency which will result in a runtime error, because the injector doesn't // exist yet. We work around the issue by creating the ComponentFactoryResolver ourselves // and providing it, rather than letting the injector resolve it. this.componentFactoryResolver = new ComponentFactoryResolver(this); const ngModuleDef = getNgModuleDef(ngModuleType); ngDevMode && assertDefined(ngModuleDef, `NgModule '${stringify(ngModuleType)}' is not a subtype of 'NgModuleType'.`); this._bootstrapComponents = maybeUnwrapFn(ngModuleDef.bootstrap); this._r3Injector = createInjectorWithoutInjectorInstances(ngModuleType, _parent, [ { provide: NgModuleRef$1, useValue: this }, { provide: ComponentFactoryResolver$1, useValue: this.componentFactoryResolver }, ...additionalProviders ], stringify(ngModuleType), new Set(['environment'])); // We need to resolve the injector types separately from the injector creation, because // the module might be trying to use this ref in its constructor for DI which will cause a // circular error that will eventually error out, because the injector isn't created yet. this._r3Injector.resolveInjectorInitializers(); this.instance = this._r3Injector.get(ngModuleType); } get injector() { return this._r3Injector; } destroy() { ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed'); const injector = this._r3Injector; !injector.destroyed && injector.destroy(); this.destroyCbs.forEach(fn => fn()); this.destroyCbs = null; } onDestroy(callback) { ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed'); this.destroyCbs.push(callback); } } exports('ɵRender3NgModuleRef', NgModuleRef); class NgModuleFactory extends NgModuleFactory$1 { constructor(moduleType) { super(); this.moduleType = moduleType; } create(parentInjector) { return new NgModuleRef(this.moduleType, parentInjector, []); } } exports('ɵNgModuleFactory', NgModuleFactory); function createNgModuleRefWithProviders(moduleType, parentInjector, additionalProviders) { return new NgModuleRef(moduleType, parentInjector, additionalProviders); } class EnvironmentNgModuleRefAdapter extends NgModuleRef$1 { constructor(config) { super(); this.componentFactoryResolver = new ComponentFactoryResolver(this); this.instance = null; const injector = new R3Injector([ ...config.providers, { provide: NgModuleRef$1, useValue: this }, { provide: ComponentFactoryResolver$1, useValue: this.componentFactoryResolver }, ], config.parent || getNullInjector(), config.debugName, new Set(['environment'])); this.injector = injector; if (config.runEnvironmentInitializers) { injector.resolveInjectorInitializers(); } } destroy() { this.injector.destroy(); } onDestroy(callback) { this.injector.onDestroy(callback); } } /** * Create a new environment injector. * * Learn more about environment injectors in * [this guide](guide/standalone-components#environment-injectors). * * @param providers An array of providers. * @param parent A parent environment injector. * @param debugName An optional name for this injector instance, which will be used in error * messages. * * @publicApi */ function createEnvironmentInjector(providers, parent, debugName = null) { const adapter = new EnvironmentNgModuleRefAdapter({ providers, parent, debugName, runEnvironmentInitializers: true }); return adapter.injector; } /** * A service used by the framework to create instances of standalone injectors. Those injectors are * created on demand in case of dynamic component instantiation and contain ambient providers * collected from the imports graph rooted at a given standalone component. */ class StandaloneService { constructor(_injector) { this._injector = _injector; this.cachedInjectors = new Map(); } getOrCreateStandaloneInjector(componentDef) { if (!componentDef.standalone) { return null; } if (!this.cachedInjectors.has(componentDef.id)) { const providers = internalImportProvidersFrom(false, componentDef.type); const standaloneInjector = providers.length > 0 ? createEnvironmentInjector([providers], this._injector, `Standalone[${componentDef.type.name}]`) : null; this.cachedInjectors.set(componentDef.id, standaloneInjector); } return this.cachedInjectors.get(componentDef.id); } ngOnDestroy() { try { for (const injector of this.cachedInjectors.values()) { if (injector !== null) { injector.destroy(); } } } finally { this.cachedInjectors.clear(); } } /** @nocollapse */ static { this.ɵprov = ɵɵdefineInjectable({ token: StandaloneService, providedIn: 'environment', factory: () => new StandaloneService(ɵɵinject(EnvironmentInjector)), }); } } /** * A feature that acts as a setup code for the {@link StandaloneService}. * * The most important responsibility of this feature is to expose the "getStandaloneInjector" * function (an entry points to a standalone injector creation) on a component definition object. We * go through the features infrastructure to make sure that the standalone injector creation logic * is tree-shakable and not included in applications that don't use standalone components. * * @codeGenApi */ function ɵɵStandaloneFeature(definition) { definition.getStandaloneInjector = (parentInjector) => { return parentInjector.get(StandaloneService).getOrCreateStandaloneInjector(definition); }; } /** * Retrieves the component instance associated with a given DOM element. * * @usageNotes * Given the following DOM structure: * * ```html * *
* *
*
* ``` * * Calling `getComponent` on `` will return the instance of `ChildComponent` * associated with this DOM element. * * Calling the function on `` will return the `MyApp` instance. * * * @param element DOM element from which the component should be retrieved. * @returns Component instance associated with the element or `null` if there * is no component associated with it. * * @publicApi * @globalApi ng */ function getComponent(element) { ngDevMode && assertDomElement(element); const context = getLContext(element); if (context === null) return null; if (context.component === undefined) { const lView = context.lView; if (lView === null) { return null; } context.component = getComponentAtNodeIndex(context.nodeIndex, lView); } return context.component; } /** * If inside an embedded view (e.g. `*ngIf` or `*ngFor`), retrieves the context of the embedded * view that the element is part of. Otherwise retrieves the instance of the component whose view * owns the element (in this case, the result is the same as calling `getOwningComponent`). * * @param element Element for which to get the surrounding component instance. * @returns Instance of the component that is around the element or null if the element isn't * inside any component. * * @publicApi * @globalApi ng */ function getContext(element) { assertDomElement(element); const context = getLContext(element); const lView = context ? context.lView : null; return lView === null ? null : lView[CONTEXT]; } /** * Retrieves the component instance whose view contains the DOM element. * * For example, if `` is used in the template of `` * (i.e. a `ViewChild` of ``), calling `getOwningComponent` on `` * would return ``. * * @param elementOrDir DOM element, component or directive instance * for which to retrieve the root components. * @returns Component instance whose view owns the DOM element or null if the element is not * part of a component view. * * @publicApi * @globalApi ng */ function getOwningComponent(elementOrDir) { const context = getLContext(elementOrDir); let lView = context ? context.lView : null; if (lView === null) return null; let parent; while (lView[TVIEW].type === 2 /* TViewType.Embedded */ && (parent = getLViewParent(lView))) { lView = parent; } return lView[FLAGS] & 512 /* LViewFlags.IsRoot */ ? null : lView[CONTEXT]; } /** * Retrieves all root components associated with a DOM element, directive or component instance. * Root components are those which have been bootstrapped by Angular. * * @param elementOrDir DOM element, component or directive instance * for which to retrieve the root components. * @returns Root components associated with the target object. * * @publicApi * @globalApi ng */ function getRootComponents(elementOrDir) { const lView = readPatchedLView(elementOrDir); return lView !== null ? [getRootContext(lView)] : []; } /** * Retrieves an `Injector` associated with an element, component or directive instance. * * @param elementOrDir DOM element, component or directive instance for which to * retrieve the injector. * @returns Injector associated with the element, component or directive instance. * * @publicApi * @globalApi ng */ function getInjector(elementOrDir) { const context = getLContext(elementOrDir); const lView = context ? context.lView : null; if (lView === null) return Injector.NULL; const tNode = lView[TVIEW].data[context.nodeIndex]; return new NodeInjector(tNode, lView); } /** * Retrieve a set of injection tokens at a given DOM node. * * @param element Element for which the injection tokens should be retrieved. */ function getInjectionTokens(element) { const context = getLContext(element); const lView = context ? context.lView : null; if (lView === null) return []; const tView = lView[TVIEW]; const tNode = tView.data[context.nodeIndex]; const providerTokens = []; const startIndex = tNode.providerIndexes & 1048575 /* TNodeProviderIndexes.ProvidersStartIndexMask */; const endIndex = tNode.directiveEnd; for (let i = startIndex; i < endIndex; i++) { let value = tView.data[i]; if (isDirectiveDefHack(value)) { // The fact that we sometimes store Type and sometimes DirectiveDef in this location is a // design flaw. We should always store same type so that we can be monomorphic. The issue // is that for Components/Directives we store the def instead the type. The correct behavior // is that we should always be storing injectable type in this location. value = value.type; } providerTokens.push(value); } return providerTokens; } /** * Retrieves directive instances associated with a given DOM node. Does not include * component instances. * * @usageNotes * Given the following DOM structure: * * ```html * * * * * ``` * * Calling `getDirectives` on `', * }) * class App {} * * ``` * * The following example registers another DOM event handler that listens for `Enter` key-press * events on the global `window`. * ``` ts * import { HostListener, Component } from "@angular/core"; * * @Component({ * selector: 'app', * template: `

Hello, you have pressed enter {{counter}} number of times!

Press enter key * to increment the counter. * ` * }) * class AppComponent { * counter = 0; * @HostListener('window:keydown.enter', ['$event']) * handleKeyDown(event: KeyboardEvent) { * this.counter++; * } * resetCounter() { * this.counter = 0; * } * } * ``` * The list of valid key names for `keydown` and `keyup` events * can be found here: * https://www.w3.org/TR/DOM-Level-3-Events-key/#named-key-attribute-values * * Note that keys can also be combined, e.g. `@HostListener('keydown.shift.a')`. * * The global target names that can be used to prefix an event name are * `document:`, `window:` and `body:`. * * @Annotation * @publicApi */ const HostListener = exports('HostListener', makePropDecorator('HostListener', (eventName, args) => ({ eventName, args }))); /** * @Annotation */ const NgModule = exports('NgModule', makeDecorator('NgModule', (ngModule) => ngModule, undefined, undefined, /** * Decorator that marks the following class as an NgModule, and supplies * configuration metadata for it. * * * The `declarations` option configures the compiler * with information about what belongs to the NgModule. * * The `providers` options configures the NgModule's injector to provide * dependencies the NgModule members. * * The `imports` and `exports` options bring in members from other modules, and make * this module's members available to others. */ (type, meta) => compileNgModule(type, meta))); /** * A [DI token](guide/glossary#di-token "DI token definition") that you can use to provide * one or more initialization functions. * * The provided functions are injected at application startup and executed during * app initialization. If any of these functions returns a Promise or an Observable, initialization * does not complete until the Promise is resolved or the Observable is completed. * * You can, for example, create a factory function that loads language data * or an external configuration, and provide that function to the `APP_INITIALIZER` token. * The function is executed during the application bootstrap process, * and the needed data is available on startup. * * @see `ApplicationInitStatus` * * @usageNotes * * The following example illustrates how to configure a multi-provider using `APP_INITIALIZER` token * and a function returning a promise. * * ``` * function initializeApp(): Promise { * return new Promise((resolve, reject) => { * // Do some asynchronous stuff * resolve(); * }); * } * * @NgModule({ * imports: [BrowserModule], * declarations: [AppComponent], * bootstrap: [AppComponent], * providers: [{ * provide: APP_INITIALIZER, * useFactory: () => initializeApp, * multi: true * }] * }) * export class AppModule {} * ``` * * It's also possible to configure a multi-provider using `APP_INITIALIZER` token and a function * returning an observable, see an example below. Note: the `HttpClient` in this example is used for * demo purposes to illustrate how the factory function can work with other providers available * through DI. * * ``` * function initializeAppFactory(httpClient: HttpClient): () => Observable { * return () => httpClient.get("https://someUrl.com/api/user") * .pipe( * tap(user => { ... }) * ); * } * * @NgModule({ * imports: [BrowserModule, HttpClientModule], * declarations: [AppComponent], * bootstrap: [AppComponent], * providers: [{ * provide: APP_INITIALIZER, * useFactory: initializeAppFactory, * deps: [HttpClient], * multi: true * }] * }) * export class AppModule {} * ``` * * @publicApi */ const APP_INITIALIZER = exports('APP_INITIALIZER', new InjectionToken('Application Initializer')); /** * A class that reflects the state of running {@link APP_INITIALIZER} functions. * * @publicApi */ class ApplicationInitStatus { constructor() { this.initialized = false; this.done = false; this.donePromise = new Promise((res, rej) => { this.resolve = res; this.reject = rej; }); this.appInits = inject(APP_INITIALIZER, { optional: true }) ?? []; if ((typeof ngDevMode === 'undefined' || ngDevMode) && !Array.isArray(this.appInits)) { throw new RuntimeError(-209 /* RuntimeErrorCode.INVALID_MULTI_PROVIDER */, 'Unexpected type of the `APP_INITIALIZER` token value ' + `(expected an array, but got ${typeof this.appInits}). ` + 'Please check that the `APP_INITIALIZER` token is configured as a ' + '`multi: true` provider.'); } } /** @internal */ runInitializers() { if (this.initialized) { return; } const asyncInitPromises = []; for (const appInits of this.appInits) { const initResult = appInits(); if (isPromise(initResult)) { asyncInitPromises.push(initResult); } else if (isSubscribable(initResult)) { const observableAsPromise = new Promise((resolve, reject) => { initResult.subscribe({ complete: resolve, error: reject }); }); asyncInitPromises.push(observableAsPromise); } } const complete = () => { // @ts-expect-error overwriting a readonly this.done = true; this.resolve(); }; Promise.all(asyncInitPromises) .then(() => { complete(); }) .catch(e => { this.reject(e); }); if (asyncInitPromises.length === 0) { complete(); } this.initialized = true; } static { this.ɵfac = function ApplicationInitStatus_Factory(t) { return new (t || ApplicationInitStatus)(); }; } static { this.ɵprov = /*@__PURE__*/ ɵɵdefineInjectable({ token: ApplicationInitStatus, factory: ApplicationInitStatus.ɵfac, providedIn: 'root' }); } } exports('ApplicationInitStatus', ApplicationInitStatus); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ApplicationInitStatus, [{ type: Injectable, args: [{ providedIn: 'root' }] }], function () { return []; }, null); })(); class Console { log(message) { // tslint:disable-next-line:no-console console.log(message); } // Note: for reporting errors use `DOM.logError()` as it is platform specific warn(message) { // tslint:disable-next-line:no-console console.warn(message); } static { this.ɵfac = function Console_Factory(t) { return new (t || Console)(); }; } static { this.ɵprov = /*@__PURE__*/ ɵɵdefineInjectable({ token: Console, factory: Console.ɵfac, providedIn: 'platform' }); } } exports('ɵConsole', Console); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(Console, [{ type: Injectable, args: [{ providedIn: 'platform' }] }], null, null); })(); /** * Work out the locale from the potential global properties. * * * Closure Compiler: use `goog.LOCALE`. * * Ivy enabled: use `$localize.locale` */ function getGlobalLocale() { if (typeof ngI18nClosureMode !== 'undefined' && ngI18nClosureMode && typeof goog !== 'undefined' && goog.LOCALE !== 'en') { // * The default `goog.LOCALE` value is `en`, while Angular used `en-US`. // * In order to preserve backwards compatibility, we use Angular default value over // Closure Compiler's one. return goog.LOCALE; } else { // KEEP `typeof $localize !== 'undefined' && $localize.locale` IN SYNC WITH THE LOCALIZE // COMPILE-TIME INLINER. // // * During compile time inlining of translations the expression will be replaced // with a string literal that is the current locale. Other forms of this expression are not // guaranteed to be replaced. // // * During runtime translation evaluation, the developer is required to set `$localize.locale` // if required, or just to provide their own `LOCALE_ID` provider. return (typeof $localize !== 'undefined' && $localize.locale) || DEFAULT_LOCALE_ID; } } /** * Provide this token to set the locale of your application. * It is used for i18n extraction, by i18n pipes (DatePipe, I18nPluralPipe, CurrencyPipe, * DecimalPipe and PercentPipe) and by ICU expressions. * * See the [i18n guide](guide/i18n-common-locale-id) for more information. * * @usageNotes * ### Example * * ```typescript * import { LOCALE_ID } from '@angular/core'; * import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; * import { AppModule } from './app/app.module'; * * platformBrowserDynamic().bootstrapModule(AppModule, { * providers: [{provide: LOCALE_ID, useValue: 'en-US' }] * }); * ``` * * @publicApi */ const LOCALE_ID = exports('LOCALE_ID', new InjectionToken('LocaleId', { providedIn: 'root', factory: () => inject(LOCALE_ID, InjectFlags.Optional | InjectFlags.SkipSelf) || getGlobalLocale(), })); /** * Provide this token to set the default currency code your application uses for * CurrencyPipe when there is no currency code passed into it. This is only used by * CurrencyPipe and has no relation to locale currency. Defaults to USD if not configured. * * See the [i18n guide](guide/i18n-common-locale-id) for more information. * *
* * **Deprecation notice:** * * The default currency code is currently always `USD` but this is deprecated from v9. * * **In v10 the default currency code will be taken from the current locale.** * * If you need the previous behavior then set it by creating a `DEFAULT_CURRENCY_CODE` provider in * your application `NgModule`: * * ```ts * {provide: DEFAULT_CURRENCY_CODE, useValue: 'USD'} * ``` * *
* * @usageNotes * ### Example * * ```typescript * import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; * import { AppModule } from './app/app.module'; * * platformBrowserDynamic().bootstrapModule(AppModule, { * providers: [{provide: DEFAULT_CURRENCY_CODE, useValue: 'EUR' }] * }); * ``` * * @publicApi */ const DEFAULT_CURRENCY_CODE = exports('DEFAULT_CURRENCY_CODE', new InjectionToken('DefaultCurrencyCode', { providedIn: 'root', factory: () => USD_CURRENCY_CODE, })); /** * Use this token at bootstrap to provide the content of your translation file (`xtb`, * `xlf` or `xlf2`) when you want to translate your application in another language. * * See the [i18n guide](guide/i18n-common-merge) for more information. * * @usageNotes * ### Example * * ```typescript * import { TRANSLATIONS } from '@angular/core'; * import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; * import { AppModule } from './app/app.module'; * * // content of your translation file * const translations = '....'; * * platformBrowserDynamic().bootstrapModule(AppModule, { * providers: [{provide: TRANSLATIONS, useValue: translations }] * }); * ``` * * @publicApi */ const TRANSLATIONS = exports('TRANSLATIONS', new InjectionToken('Translations')); /** * Provide this token at bootstrap to set the format of your {@link TRANSLATIONS}: `xtb`, * `xlf` or `xlf2`. * * See the [i18n guide](guide/i18n-common-merge) for more information. * * @usageNotes * ### Example * * ```typescript * import { TRANSLATIONS_FORMAT } from '@angular/core'; * import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; * import { AppModule } from './app/app.module'; * * platformBrowserDynamic().bootstrapModule(AppModule, { * providers: [{provide: TRANSLATIONS_FORMAT, useValue: 'xlf' }] * }); * ``` * * @publicApi */ const TRANSLATIONS_FORMAT = exports('TRANSLATIONS_FORMAT', new InjectionToken('TranslationsFormat')); /** * Use this enum at bootstrap as an option of `bootstrapModule` to define the strategy * that the compiler should use in case of missing translations: * - Error: throw if you have missing translations. * - Warning (default): show a warning in the console and/or shell. * - Ignore: do nothing. * * See the [i18n guide](guide/i18n-common-merge#report-missing-translations) for more information. * * @usageNotes * ### Example * ```typescript * import { MissingTranslationStrategy } from '@angular/core'; * import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; * import { AppModule } from './app/app.module'; * * platformBrowserDynamic().bootstrapModule(AppModule, { * missingTranslation: MissingTranslationStrategy.Error * }); * ``` * * @publicApi */ var MissingTranslationStrategy; exports('MissingTranslationStrategy', MissingTranslationStrategy); (function (MissingTranslationStrategy) { MissingTranslationStrategy[MissingTranslationStrategy["Error"] = 0] = "Error"; MissingTranslationStrategy[MissingTranslationStrategy["Warning"] = 1] = "Warning"; MissingTranslationStrategy[MissingTranslationStrategy["Ignore"] = 2] = "Ignore"; })(MissingTranslationStrategy || (exports('MissingTranslationStrategy', MissingTranslationStrategy = {}))); /** * *Internal* service that keeps track of pending tasks happening in the system * during the initial rendering. No tasks are tracked after an initial * rendering. * * This information is needed to make sure that the serialization on the server * is delayed until all tasks in the queue (such as an initial navigation or a * pending HTTP request) are completed. */ class InitialRenderPendingTasks { constructor() { this.taskId = 0; this.pendingTasks = new Set(); this.hasPendingTasks = new BehaviorSubject(false); } add() { this.hasPendingTasks.next(true); const taskId = this.taskId++; this.pendingTasks.add(taskId); return taskId; } remove(taskId) { this.pendingTasks.delete(taskId); if (this.pendingTasks.size === 0) { this.hasPendingTasks.next(false); } } ngOnDestroy() { this.pendingTasks.clear(); this.hasPendingTasks.next(false); } static { this.ɵfac = function InitialRenderPendingTasks_Factory(t) { return new (t || InitialRenderPendingTasks)(); }; } static { this.ɵprov = /*@__PURE__*/ ɵɵdefineInjectable({ token: InitialRenderPendingTasks, factory: InitialRenderPendingTasks.ɵfac, providedIn: 'root' }); } } exports('ɵInitialRenderPendingTasks', InitialRenderPendingTasks); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(InitialRenderPendingTasks, [{ type: Injectable, args: [{ providedIn: 'root' }] }], null, null); })(); /** * Combination of NgModuleFactory and ComponentFactories. * * @publicApi * * @deprecated * Ivy JIT mode doesn't require accessing this symbol. * See [JIT API changes due to ViewEngine deprecation](guide/deprecations#jit-api-changes) for * additional context. */ class ModuleWithComponentFactories { constructor(ngModuleFactory, componentFactories) { this.ngModuleFactory = ngModuleFactory; this.componentFactories = componentFactories; } } exports('ModuleWithComponentFactories', ModuleWithComponentFactories); /** * Low-level service for running the angular compiler during runtime * to create {@link ComponentFactory}s, which * can later be used to create and render a Component instance. * * Each `@NgModule` provides an own `Compiler` to its injector, * that will use the directives/pipes of the ng module for compilation * of components. * * @publicApi * * @deprecated * Ivy JIT mode doesn't require accessing this symbol. * See [JIT API changes due to ViewEngine deprecation](guide/deprecations#jit-api-changes) for * additional context. */ class Compiler { /** * Compiles the given NgModule and all of its components. All templates of the components * have to be inlined. */ compileModuleSync(moduleType) { return new NgModuleFactory(moduleType); } /** * Compiles the given NgModule and all of its components */ compileModuleAsync(moduleType) { return Promise.resolve(this.compileModuleSync(moduleType)); } /** * Same as {@link #compileModuleSync} but also creates ComponentFactories for all components. */ compileModuleAndAllComponentsSync(moduleType) { const ngModuleFactory = this.compileModuleSync(moduleType); const moduleDef = getNgModuleDef(moduleType); const componentFactories = maybeUnwrapFn(moduleDef.declarations) .reduce((factories, declaration) => { const componentDef = getComponentDef(declaration); componentDef && factories.push(new ComponentFactory(componentDef)); return factories; }, []); return new ModuleWithComponentFactories(ngModuleFactory, componentFactories); } /** * Same as {@link #compileModuleAsync} but also creates ComponentFactories for all components. */ compileModuleAndAllComponentsAsync(moduleType) { return Promise.resolve(this.compileModuleAndAllComponentsSync(moduleType)); } /** * Clears all caches. */ clearCache() { } /** * Clears the cache for the given component/ngModule. */ clearCacheFor(type) { } /** * Returns the id for a given NgModule, if one is defined and known to the compiler. */ getModuleId(moduleType) { return undefined; } static { this.ɵfac = function Compiler_Factory(t) { return new (t || Compiler)(); }; } static { this.ɵprov = /*@__PURE__*/ ɵɵdefineInjectable({ token: Compiler, factory: Compiler.ɵfac, providedIn: 'root' }); } } exports('Compiler', Compiler); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(Compiler, [{ type: Injectable, args: [{ providedIn: 'root' }] }], null, null); })(); /** * Token to provide CompilerOptions in the platform injector. * * @publicApi */ const COMPILER_OPTIONS = exports('COMPILER_OPTIONS', new InjectionToken('compilerOptions')); /** * A factory for creating a Compiler * * @publicApi * * @deprecated * Ivy JIT mode doesn't require accessing this symbol. * See [JIT API changes due to ViewEngine deprecation](guide/deprecations#jit-api-changes) for * additional context. */ class CompilerFactory { } exports('CompilerFactory', CompilerFactory); /** * Marks a component for check (in case of OnPush components) and synchronously * performs change detection on the application this component belongs to. * * @param component Component to {@link ChangeDetectorRef#markForCheck mark for check}. * * @publicApi * @globalApi ng */ function applyChanges(component) { ngDevMode && assertDefined(component, 'component'); markViewDirty(getComponentViewByInstance(component)); getRootComponents(component).forEach(rootComponent => detectChanges(rootComponent)); } /** * This file introduces series of globally accessible debug tools * to allow for the Angular debugging story to function. * * To see this in action run the following command: * * bazel run //packages/core/test/bundling/todo:devserver * * Then load `localhost:5432` and start using the console tools. */ /** * This value reflects the property on the window where the dev * tools are patched (window.ng). * */ const GLOBAL_PUBLISH_EXPANDO_KEY = 'ng'; let _published = false; /** * Publishes a collection of default debug tools onto`window.ng`. * * These functions are available globally when Angular is in development * mode and are automatically stripped away from prod mode is on. */ function publishDefaultGlobalUtils$1() { if (!_published) { _published = true; /** * Warning: this function is *INTERNAL* and should not be relied upon in application's code. * The contract of the function might be changed in any release and/or the function can be * removed completely. */ publishGlobalUtil('ɵsetProfiler', setProfiler); publishGlobalUtil('getDirectiveMetadata', getDirectiveMetadata$1); publishGlobalUtil('getComponent', getComponent); publishGlobalUtil('getContext', getContext); publishGlobalUtil('getListeners', getListeners); publishGlobalUtil('getOwningComponent', getOwningComponent); publishGlobalUtil('getHostElement', getHostElement); publishGlobalUtil('getInjector', getInjector); publishGlobalUtil('getRootComponents', getRootComponents); publishGlobalUtil('getDirectives', getDirectives); publishGlobalUtil('applyChanges', applyChanges); } } /** * Publishes the given function to `window.ng` so that it can be * used from the browser console when an application is not in production. */ function publishGlobalUtil(name, fn) { if (typeof COMPILED === 'undefined' || !COMPILED) { // Note: we can't export `ng` when using closure enhanced optimization as: // - closure declares globals itself for minified names, which sometimes clobber our `ng` global // - we can't declare a closure extern as the namespace `ng` is already used within Google // for typings for AngularJS (via `goog.provide('ng....')`). const w = _global; ngDevMode && assertDefined(fn, 'function not defined'); if (w) { let container = w[GLOBAL_PUBLISH_EXPANDO_KEY]; if (!container) { container = w[GLOBAL_PUBLISH_EXPANDO_KEY] = {}; } container[name] = fn; } } } const promise = (() => Promise.resolve(0))(); function scheduleMicroTask(fn) { if (typeof Zone === 'undefined') { // use promise to schedule microTask instead of use Zone promise.then(() => { fn && fn.apply(null, null); }); } else { Zone.current.scheduleMicroTask('scheduleMicrotask', fn); } } function noop(...args) { // Do nothing. } function getNativeRequestAnimationFrame() { let nativeRequestAnimationFrame = _global['requestAnimationFrame']; let nativeCancelAnimationFrame = _global['cancelAnimationFrame']; if (typeof Zone !== 'undefined' && nativeRequestAnimationFrame && nativeCancelAnimationFrame) { // use unpatched version of requestAnimationFrame(native delegate) if possible // to avoid another Change detection const unpatchedRequestAnimationFrame = nativeRequestAnimationFrame[Zone.__symbol__('OriginalDelegate')]; if (unpatchedRequestAnimationFrame) { nativeRequestAnimationFrame = unpatchedRequestAnimationFrame; } const unpatchedCancelAnimationFrame = nativeCancelAnimationFrame[Zone.__symbol__('OriginalDelegate')]; if (unpatchedCancelAnimationFrame) { nativeCancelAnimationFrame = unpatchedCancelAnimationFrame; } } return { nativeRequestAnimationFrame, nativeCancelAnimationFrame }; } class AsyncStackTaggingZoneSpec { constructor(namePrefix, consoleAsyncStackTaggingImpl = console) { this.name = 'asyncStackTagging for ' + namePrefix; this.createTask = consoleAsyncStackTaggingImpl?.createTask ?? (() => null); } onScheduleTask(delegate, _current, target, task) { task.consoleTask = this.createTask(`Zone - ${task.source || task.type}`); return delegate.scheduleTask(target, task); } onInvokeTask(delegate, _currentZone, targetZone, task, applyThis, applyArgs) { let ret; if (task.consoleTask) { ret = task.consoleTask.run(() => delegate.invokeTask(targetZone, task, applyThis, applyArgs)); } else { ret = delegate.invokeTask(targetZone, task, applyThis, applyArgs); } return ret; } } /** * An injectable service for executing work inside or outside of the Angular zone. * * The most common use of this service is to optimize performance when starting a work consisting of * one or more asynchronous tasks that don't require UI updates or error handling to be handled by * Angular. Such tasks can be kicked off via {@link #runOutsideAngular} and if needed, these tasks * can reenter the Angular zone via {@link #run}. * * * * @usageNotes * ### Example * * ``` * import {Component, NgZone} from '@angular/core'; * import {NgIf} from '@angular/common'; * * @Component({ * selector: 'ng-zone-demo', * template: ` *

Demo: NgZone

* *

Progress: {{progress}}%

*

= 100">Done processing {{label}} of Angular zone!

* * * * `, * }) * export class NgZoneDemo { * progress: number = 0; * label: string; * * constructor(private _ngZone: NgZone) {} * * // Loop inside the Angular zone * // so the UI DOES refresh after each setTimeout cycle * processWithinAngularZone() { * this.label = 'inside'; * this.progress = 0; * this._increaseProgress(() => console.log('Inside Done!')); * } * * // Loop outside of the Angular zone * // so the UI DOES NOT refresh after each setTimeout cycle * processOutsideOfAngularZone() { * this.label = 'outside'; * this.progress = 0; * this._ngZone.runOutsideAngular(() => { * this._increaseProgress(() => { * // reenter the Angular zone and display done * this._ngZone.run(() => { console.log('Outside Done!'); }); * }); * }); * } * * _increaseProgress(doneCallback: () => void) { * this.progress += 1; * console.log(`Current progress: ${this.progress}%`); * * if (this.progress < 100) { * window.setTimeout(() => this._increaseProgress(doneCallback), 10); * } else { * doneCallback(); * } * } * } * ``` * * @publicApi */ class NgZone { constructor({ enableLongStackTrace = false, shouldCoalesceEventChangeDetection = false, shouldCoalesceRunChangeDetection = false }) { this.hasPendingMacrotasks = false; this.hasPendingMicrotasks = false; /** * Whether there are no outstanding microtasks or macrotasks. */ this.isStable = true; /** * Notifies when code enters Angular Zone. This gets fired first on VM Turn. */ this.onUnstable = new EventEmitter(false); /** * Notifies when there is no more microtasks enqueued in the current VM Turn. * This is a hint for Angular to do change detection, which may enqueue more microtasks. * For this reason this event can fire multiple times per VM Turn. */ this.onMicrotaskEmpty = new EventEmitter(false); /** * Notifies when the last `onMicrotaskEmpty` has run and there are no more microtasks, which * implies we are about to relinquish VM turn. * This event gets called just once. */ this.onStable = new EventEmitter(false); /** * Notifies that an error has been delivered. */ this.onError = new EventEmitter(false); if (typeof Zone == 'undefined') { throw new RuntimeError(908 /* RuntimeErrorCode.MISSING_ZONEJS */, ngDevMode && `In this configuration Angular requires Zone.js`); } Zone.assertZonePatched(); const self = this; self._nesting = 0; self._outer = self._inner = Zone.current; // AsyncStackTaggingZoneSpec provides `linked stack traces` to show // where the async operation is scheduled. For more details, refer // to this article, https://developer.chrome.com/blog/devtools-better-angular-debugging/ // And we only import this AsyncStackTaggingZoneSpec in development mode, // in the production mode, the AsyncStackTaggingZoneSpec will be tree shaken away. if (ngDevMode) { self._inner = self._inner.fork(new AsyncStackTaggingZoneSpec('Angular')); } if (Zone['TaskTrackingZoneSpec']) { self._inner = self._inner.fork(new Zone['TaskTrackingZoneSpec']); } if (enableLongStackTrace && Zone['longStackTraceZoneSpec']) { self._inner = self._inner.fork(Zone['longStackTraceZoneSpec']); } // if shouldCoalesceRunChangeDetection is true, all tasks including event tasks will be // coalesced, so shouldCoalesceEventChangeDetection option is not necessary and can be skipped. self.shouldCoalesceEventChangeDetection = !shouldCoalesceRunChangeDetection && shouldCoalesceEventChangeDetection; self.shouldCoalesceRunChangeDetection = shouldCoalesceRunChangeDetection; self.lastRequestAnimationFrameId = -1; self.nativeRequestAnimationFrame = getNativeRequestAnimationFrame().nativeRequestAnimationFrame; forkInnerZoneWithAngularBehavior(self); } static isInAngularZone() { // Zone needs to be checked, because this method might be called even when NoopNgZone is used. return typeof Zone !== 'undefined' && Zone.current.get('isAngularZone') === true; } static assertInAngularZone() { if (!NgZone.isInAngularZone()) { throw new RuntimeError(909 /* RuntimeErrorCode.UNEXPECTED_ZONE_STATE */, ngDevMode && 'Expected to be in Angular Zone, but it is not!'); } } static assertNotInAngularZone() { if (NgZone.isInAngularZone()) { throw new RuntimeError(909 /* RuntimeErrorCode.UNEXPECTED_ZONE_STATE */, ngDevMode && 'Expected to not be in Angular Zone, but it is!'); } } /** * Executes the `fn` function synchronously within the Angular zone and returns value returned by * the function. * * Running functions via `run` allows you to reenter Angular zone from a task that was executed * outside of the Angular zone (typically started via {@link #runOutsideAngular}). * * Any future tasks or microtasks scheduled from within this function will continue executing from * within the Angular zone. * * If a synchronous error happens it will be rethrown and not reported via `onError`. */ run(fn, applyThis, applyArgs) { return this._inner.run(fn, applyThis, applyArgs); } /** * Executes the `fn` function synchronously within the Angular zone as a task and returns value * returned by the function. * * Running functions via `run` allows you to reenter Angular zone from a task that was executed * outside of the Angular zone (typically started via {@link #runOutsideAngular}). * * Any future tasks or microtasks scheduled from within this function will continue executing from * within the Angular zone. * * If a synchronous error happens it will be rethrown and not reported via `onError`. */ runTask(fn, applyThis, applyArgs, name) { const zone = this._inner; const task = zone.scheduleEventTask('NgZoneEvent: ' + name, fn, EMPTY_PAYLOAD, noop, noop); try { return zone.runTask(task, applyThis, applyArgs); } finally { zone.cancelTask(task); } } /** * Same as `run`, except that synchronous errors are caught and forwarded via `onError` and not * rethrown. */ runGuarded(fn, applyThis, applyArgs) { return this._inner.runGuarded(fn, applyThis, applyArgs); } /** * Executes the `fn` function synchronously in Angular's parent zone and returns value returned by * the function. * * Running functions via {@link #runOutsideAngular} allows you to escape Angular's zone and do * work that * doesn't trigger Angular change-detection or is subject to Angular's error handling. * * Any future tasks or microtasks scheduled from within this function will continue executing from * outside of the Angular zone. * * Use {@link #run} to reenter the Angular zone and do work that updates the application model. */ runOutsideAngular(fn) { return this._outer.run(fn); } } exports('NgZone', NgZone); const EMPTY_PAYLOAD = {}; function checkStable(zone) { // TODO: @JiaLiPassion, should check zone.isCheckStableRunning to prevent // re-entry. The case is: // // @Component({...}) // export class AppComponent { // constructor(private ngZone: NgZone) { // this.ngZone.onStable.subscribe(() => { // this.ngZone.run(() => console.log('stable');); // }); // } // // The onStable subscriber run another function inside ngZone // which causes `checkStable()` re-entry. // But this fix causes some issues in g3, so this fix will be // launched in another PR. if (zone._nesting == 0 && !zone.hasPendingMicrotasks && !zone.isStable) { try { zone._nesting++; zone.onMicrotaskEmpty.emit(null); } finally { zone._nesting--; if (!zone.hasPendingMicrotasks) { try { zone.runOutsideAngular(() => zone.onStable.emit(null)); } finally { zone.isStable = true; } } } } } function delayChangeDetectionForEvents(zone) { /** * We also need to check _nesting here * Consider the following case with shouldCoalesceRunChangeDetection = true * * ngZone.run(() => {}); * ngZone.run(() => {}); * * We want the two `ngZone.run()` only trigger one change detection * when shouldCoalesceRunChangeDetection is true. * And because in this case, change detection run in async way(requestAnimationFrame), * so we also need to check the _nesting here to prevent multiple * change detections. */ if (zone.isCheckStableRunning || zone.lastRequestAnimationFrameId !== -1) { return; } zone.lastRequestAnimationFrameId = zone.nativeRequestAnimationFrame.call(_global, () => { // This is a work around for https://github.com/angular/angular/issues/36839. // The core issue is that when event coalescing is enabled it is possible for microtasks // to get flushed too early (As is the case with `Promise.then`) between the // coalescing eventTasks. // // To workaround this we schedule a "fake" eventTask before we process the // coalescing eventTasks. The benefit of this is that the "fake" container eventTask // will prevent the microtasks queue from getting drained in between the coalescing // eventTask execution. if (!zone.fakeTopEventTask) { zone.fakeTopEventTask = Zone.root.scheduleEventTask('fakeTopEventTask', () => { zone.lastRequestAnimationFrameId = -1; updateMicroTaskStatus(zone); zone.isCheckStableRunning = true; checkStable(zone); zone.isCheckStableRunning = false; }, undefined, () => { }, () => { }); } zone.fakeTopEventTask.invoke(); }); updateMicroTaskStatus(zone); } function forkInnerZoneWithAngularBehavior(zone) { const delayChangeDetectionForEventsDelegate = () => { delayChangeDetectionForEvents(zone); }; zone._inner = zone._inner.fork({ name: 'angular', properties: { 'isAngularZone': true }, onInvokeTask: (delegate, current, target, task, applyThis, applyArgs) => { try { onEnter(zone); return delegate.invokeTask(target, task, applyThis, applyArgs); } finally { if ((zone.shouldCoalesceEventChangeDetection && task.type === 'eventTask') || zone.shouldCoalesceRunChangeDetection) { delayChangeDetectionForEventsDelegate(); } onLeave(zone); } }, onInvoke: (delegate, current, target, callback, applyThis, applyArgs, source) => { try { onEnter(zone); return delegate.invoke(target, callback, applyThis, applyArgs, source); } finally { if (zone.shouldCoalesceRunChangeDetection) { delayChangeDetectionForEventsDelegate(); } onLeave(zone); } }, onHasTask: (delegate, current, target, hasTaskState) => { delegate.hasTask(target, hasTaskState); if (current === target) { // We are only interested in hasTask events which originate from our zone // (A child hasTask event is not interesting to us) if (hasTaskState.change == 'microTask') { zone._hasPendingMicrotasks = hasTaskState.microTask; updateMicroTaskStatus(zone); checkStable(zone); } else if (hasTaskState.change == 'macroTask') { zone.hasPendingMacrotasks = hasTaskState.macroTask; } } }, onHandleError: (delegate, current, target, error) => { delegate.handleError(target, error); zone.runOutsideAngular(() => zone.onError.emit(error)); return false; } }); } function updateMicroTaskStatus(zone) { if (zone._hasPendingMicrotasks || ((zone.shouldCoalesceEventChangeDetection || zone.shouldCoalesceRunChangeDetection) && zone.lastRequestAnimationFrameId !== -1)) { zone.hasPendingMicrotasks = true; } else { zone.hasPendingMicrotasks = false; } } function onEnter(zone) { zone._nesting++; if (zone.isStable) { zone.isStable = false; zone.onUnstable.emit(null); } } function onLeave(zone) { zone._nesting--; checkStable(zone); } /** * Provides a noop implementation of `NgZone` which does nothing. This zone requires explicit calls * to framework to perform rendering. */ class NoopNgZone { constructor() { this.hasPendingMicrotasks = false; this.hasPendingMacrotasks = false; this.isStable = true; this.onUnstable = new EventEmitter(); this.onMicrotaskEmpty = new EventEmitter(); this.onStable = new EventEmitter(); this.onError = new EventEmitter(); } run(fn, applyThis, applyArgs) { return fn.apply(applyThis, applyArgs); } runGuarded(fn, applyThis, applyArgs) { return fn.apply(applyThis, applyArgs); } runOutsideAngular(fn) { return fn(); } runTask(fn, applyThis, applyArgs, name) { return fn.apply(applyThis, applyArgs); } } exports('ɵNoopNgZone', NoopNgZone); /** * Token used to drive ApplicationRef.isStable * * TODO: This should be moved entirely to NgZone (as a breaking change) so it can be tree-shakeable * for `NoopNgZone` which is always just an `Observable` of `true`. Additionally, we should consider * whether the property on `NgZone` should be `Observable` or `Signal`. */ const ZONE_IS_STABLE_OBSERVABLE = new InjectionToken(ngDevMode ? 'isStable Observable' : '', { providedIn: 'root', // TODO(atscott): Replace this with a suitable default like `new // BehaviorSubject(true).asObservable`. Again, long term this won't exist on ApplicationRef at // all but until we can remove it, we need a default value zoneless. factory: isStableFactory, }); function isStableFactory() { const zone = inject(NgZone); let _stable = true; const isCurrentlyStable = new Observable((observer) => { _stable = zone.isStable && !zone.hasPendingMacrotasks && !zone.hasPendingMicrotasks; zone.runOutsideAngular(() => { observer.next(_stable); observer.complete(); }); }); const isStable = new Observable((observer) => { // Create the subscription to onStable outside the Angular Zone so that // the callback is run outside the Angular Zone. let stableSub; zone.runOutsideAngular(() => { stableSub = zone.onStable.subscribe(() => { NgZone.assertNotInAngularZone(); // Check whether there are no pending macro/micro tasks in the next tick // to allow for NgZone to update the state. scheduleMicroTask(() => { if (!_stable && !zone.hasPendingMacrotasks && !zone.hasPendingMicrotasks) { _stable = true; observer.next(true); } }); }); }); const unstableSub = zone.onUnstable.subscribe(() => { NgZone.assertInAngularZone(); if (_stable) { _stable = false; zone.runOutsideAngular(() => { observer.next(false); }); } }); return () => { stableSub.unsubscribe(); unstableSub.unsubscribe(); }; }); return merge$1(isCurrentlyStable, isStable.pipe(share())); } /** * Internal injection token that can used to access an instance of a Testability class. * * This token acts as a bridge between the core bootstrap code and the `Testability` class. This is * needed to ensure that there are no direct references to the `Testability` class, so it can be * tree-shaken away (if not referenced). For the environments/setups when the `Testability` class * should be available, this token is used to add a provider that references the `Testability` * class. Otherwise, only this token is retained in a bundle, but the `Testability` class is not. */ const TESTABILITY = exports('ɵTESTABILITY', new InjectionToken('')); /** * Internal injection token to retrieve Testability getter class instance. */ const TESTABILITY_GETTER = exports('ɵTESTABILITY_GETTER', new InjectionToken('')); /** * The Testability service provides testing hooks that can be accessed from * the browser. * * Angular applications bootstrapped using an NgModule (via `@NgModule.bootstrap` field) will also * instantiate Testability by default (in both development and production modes). * * For applications bootstrapped using the `bootstrapApplication` function, Testability is not * included by default. You can include it into your applications by getting the list of necessary * providers using the `provideProtractorTestingSupport()` function and adding them into the * `options.providers` array. Example: * * ```typescript * import {provideProtractorTestingSupport} from '@angular/platform-browser'; * * await bootstrapApplication(RootComponent, providers: [provideProtractorTestingSupport()]); * ``` * * @publicApi */ class Testability { constructor(_ngZone, registry, testabilityGetter) { this._ngZone = _ngZone; this.registry = registry; this._pendingCount = 0; this._isZoneStable = true; /** * Whether any work was done since the last 'whenStable' callback. This is * useful to detect if this could have potentially destabilized another * component while it is stabilizing. * @internal */ this._didWork = false; this._callbacks = []; this.taskTrackingZone = null; // If there was no Testability logic registered in the global scope // before, register the current testability getter as a global one. if (!_testabilityGetter) { setTestabilityGetter(testabilityGetter); testabilityGetter.addToWindow(registry); } this._watchAngularEvents(); _ngZone.run(() => { this.taskTrackingZone = typeof Zone == 'undefined' ? null : Zone.current.get('TaskTrackingZone'); }); } _watchAngularEvents() { this._ngZone.onUnstable.subscribe({ next: () => { this._didWork = true; this._isZoneStable = false; } }); this._ngZone.runOutsideAngular(() => { this._ngZone.onStable.subscribe({ next: () => { NgZone.assertNotInAngularZone(); scheduleMicroTask(() => { this._isZoneStable = true; this._runCallbacksIfReady(); }); } }); }); } /** * Increases the number of pending request * @deprecated pending requests are now tracked with zones. */ increasePendingRequestCount() { this._pendingCount += 1; this._didWork = true; return this._pendingCount; } /** * Decreases the number of pending request * @deprecated pending requests are now tracked with zones */ decreasePendingRequestCount() { this._pendingCount -= 1; if (this._pendingCount < 0) { throw new Error('pending async requests below zero'); } this._runCallbacksIfReady(); return this._pendingCount; } /** * Whether an associated application is stable */ isStable() { return this._isZoneStable && this._pendingCount === 0 && !this._ngZone.hasPendingMacrotasks; } _runCallbacksIfReady() { if (this.isStable()) { // Schedules the call backs in a new frame so that it is always async. scheduleMicroTask(() => { while (this._callbacks.length !== 0) { let cb = this._callbacks.pop(); clearTimeout(cb.timeoutId); cb.doneCb(this._didWork); } this._didWork = false; }); } else { // Still not stable, send updates. let pending = this.getPendingTasks(); this._callbacks = this._callbacks.filter((cb) => { if (cb.updateCb && cb.updateCb(pending)) { clearTimeout(cb.timeoutId); return false; } return true; }); this._didWork = true; } } getPendingTasks() { if (!this.taskTrackingZone) { return []; } // Copy the tasks data so that we don't leak tasks. return this.taskTrackingZone.macroTasks.map((t) => { return { source: t.source, // From TaskTrackingZone: // https://github.com/angular/zone.js/blob/master/lib/zone-spec/task-tracking.ts#L40 creationLocation: t.creationLocation, data: t.data }; }); } addCallback(cb, timeout, updateCb) { let timeoutId = -1; if (timeout && timeout > 0) { timeoutId = setTimeout(() => { this._callbacks = this._callbacks.filter((cb) => cb.timeoutId !== timeoutId); cb(this._didWork, this.getPendingTasks()); }, timeout); } this._callbacks.push({ doneCb: cb, timeoutId: timeoutId, updateCb: updateCb }); } /** * Wait for the application to be stable with a timeout. If the timeout is reached before that * happens, the callback receives a list of the macro tasks that were pending, otherwise null. * * @param doneCb The callback to invoke when Angular is stable or the timeout expires * whichever comes first. * @param timeout Optional. The maximum time to wait for Angular to become stable. If not * specified, whenStable() will wait forever. * @param updateCb Optional. If specified, this callback will be invoked whenever the set of * pending macrotasks changes. If this callback returns true doneCb will not be invoked * and no further updates will be issued. */ whenStable(doneCb, timeout, updateCb) { if (updateCb && !this.taskTrackingZone) { throw new Error('Task tracking zone is required when passing an update callback to ' + 'whenStable(). Is "zone.js/plugins/task-tracking" loaded?'); } // These arguments are 'Function' above to keep the public API simple. this.addCallback(doneCb, timeout, updateCb); this._runCallbacksIfReady(); } /** * Get the number of pending requests * @deprecated pending requests are now tracked with zones */ getPendingRequestCount() { return this._pendingCount; } /** * Registers an application with a testability hook so that it can be tracked. * @param token token of application, root element * * @internal */ registerApplication(token) { this.registry.registerApplication(token, this); } /** * Unregisters an application. * @param token token of application, root element * * @internal */ unregisterApplication(token) { this.registry.unregisterApplication(token); } /** * Find providers by name * @param using The root element to search from * @param provider The name of binding variable * @param exactMatch Whether using exactMatch */ findProviders(using, provider, exactMatch) { // TODO(juliemr): implement. return []; } static { this.ɵfac = function Testability_Factory(t) { return new (t || Testability)(ɵɵinject(NgZone), ɵɵinject(TestabilityRegistry), ɵɵinject(TESTABILITY_GETTER)); }; } static { this.ɵprov = /*@__PURE__*/ ɵɵdefineInjectable({ token: Testability, factory: Testability.ɵfac }); } } exports('Testability', Testability); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(Testability, [{ type: Injectable }], function () { return [{ type: NgZone }, { type: TestabilityRegistry }, { type: undefined, decorators: [{ type: Inject, args: [TESTABILITY_GETTER] }] }]; }, null); })(); /** * A global registry of {@link Testability} instances for specific elements. * @publicApi */ class TestabilityRegistry { constructor() { /** @internal */ this._applications = new Map(); } /** * Registers an application with a testability hook so that it can be tracked * @param token token of application, root element * @param testability Testability hook */ registerApplication(token, testability) { this._applications.set(token, testability); } /** * Unregisters an application. * @param token token of application, root element */ unregisterApplication(token) { this._applications.delete(token); } /** * Unregisters all applications */ unregisterAllApplications() { this._applications.clear(); } /** * Get a testability hook associated with the application * @param elem root element */ getTestability(elem) { return this._applications.get(elem) || null; } /** * Get all registered testabilities */ getAllTestabilities() { return Array.from(this._applications.values()); } /** * Get all registered applications(root elements) */ getAllRootElements() { return Array.from(this._applications.keys()); } /** * Find testability of a node in the Tree * @param elem node * @param findInAncestors whether finding testability in ancestors if testability was not found in * current node */ findTestabilityInTree(elem, findInAncestors = true) { return _testabilityGetter?.findTestabilityInTree(this, elem, findInAncestors) ?? null; } static { this.ɵfac = function TestabilityRegistry_Factory(t) { return new (t || TestabilityRegistry)(); }; } static { this.ɵprov = /*@__PURE__*/ ɵɵdefineInjectable({ token: TestabilityRegistry, factory: TestabilityRegistry.ɵfac, providedIn: 'platform' }); } } exports('TestabilityRegistry', TestabilityRegistry); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(TestabilityRegistry, [{ type: Injectable, args: [{ providedIn: 'platform' }] }], null, null); })(); /** * Set the {@link GetTestability} implementation used by the Angular testing framework. * @publicApi */ function setTestabilityGetter(getter) { _testabilityGetter = getter; } let _testabilityGetter; let _platformInjector = null; /** * Internal token to indicate whether having multiple bootstrapped platform should be allowed (only * one bootstrapped platform is allowed by default). This token helps to support SSR scenarios. */ const ALLOW_MULTIPLE_PLATFORMS = exports('ɵALLOW_MULTIPLE_PLATFORMS', new InjectionToken('AllowMultipleToken')); /** * Internal token that allows to register extra callbacks that should be invoked during the * `PlatformRef.destroy` operation. This token is needed to avoid a direct reference to the * `PlatformRef` class (i.e. register the callback via `PlatformRef.onDestroy`), thus making the * entire class tree-shakeable. */ const PLATFORM_DESTROY_LISTENERS = new InjectionToken('PlatformDestroyListeners'); /** * A [DI token](guide/glossary#di-token "DI token definition") that provides a set of callbacks to * be called for every component that is bootstrapped. * * Each callback must take a `ComponentRef` instance and return nothing. * * `(componentRef: ComponentRef) => void` * * @publicApi */ const APP_BOOTSTRAP_LISTENER = exports('APP_BOOTSTRAP_LISTENER', new InjectionToken('appBootstrapListener')); function compileNgModuleFactory(injector, options, moduleType) { ngDevMode && assertNgModuleType(moduleType); const moduleFactory = new NgModuleFactory(moduleType); // All of the logic below is irrelevant for AOT-compiled code. if (typeof ngJitMode !== 'undefined' && !ngJitMode) { return Promise.resolve(moduleFactory); } const compilerOptions = injector.get(COMPILER_OPTIONS, []).concat(options); // Configure the compiler to use the provided options. This call may fail when multiple modules // are bootstrapped with incompatible options, as a component can only be compiled according to // a single set of options. setJitOptions({ defaultEncapsulation: _lastDefined(compilerOptions.map(opts => opts.defaultEncapsulation)), preserveWhitespaces: _lastDefined(compilerOptions.map(opts => opts.preserveWhitespaces)), }); if (isComponentResourceResolutionQueueEmpty()) { return Promise.resolve(moduleFactory); } const compilerProviders = compilerOptions.flatMap((option) => option.providers ?? []); // In case there are no compiler providers, we just return the module factory as // there won't be any resource loader. This can happen with Ivy, because AOT compiled // modules can be still passed through "bootstrapModule". In that case we shouldn't // unnecessarily require the JIT compiler. if (compilerProviders.length === 0) { return Promise.resolve(moduleFactory); } const compiler = getCompilerFacade({ usage: 0 /* JitCompilerUsage.Decorator */, kind: 'NgModule', type: moduleType, }); const compilerInjector = Injector.create({ providers: compilerProviders }); const resourceLoader = compilerInjector.get(compiler.ResourceLoader); // The resource loader can also return a string while the "resolveComponentResources" // always expects a promise. Therefore we need to wrap the returned value in a promise. return resolveComponentResources(url => Promise.resolve(resourceLoader.get(url))) .then(() => moduleFactory); } function publishDefaultGlobalUtils() { ngDevMode && publishDefaultGlobalUtils$1(); } /** * Sets the error for an invalid write to a signal to be an Angular `RuntimeError`. */ function publishSignalConfiguration() { setThrowInvalidWriteToSignalError(() => { throw new RuntimeError(600 /* RuntimeErrorCode.SIGNAL_WRITE_FROM_ILLEGAL_CONTEXT */, ngDevMode && 'Writing to signals is not allowed in a `computed` or an `effect` by default. ' + 'Use `allowSignalWrites` in the `CreateEffectOptions` to enable this inside effects.'); }); } function isBoundToModule(cf) { return cf.isBoundToModule; } /** * A token for third-party components that can register themselves with NgProbe. * * @publicApi */ class NgProbeToken { constructor(name, token) { this.name = name; this.token = token; } } exports('NgProbeToken', NgProbeToken); /** * Creates a platform. * Platforms must be created on launch using this function. * * @publicApi */ function createPlatform(injector) { if (_platformInjector && !_platformInjector.get(ALLOW_MULTIPLE_PLATFORMS, false)) { throw new RuntimeError(400 /* RuntimeErrorCode.MULTIPLE_PLATFORMS */, ngDevMode && 'There can be only one platform. Destroy the previous one to create a new one.'); } publishDefaultGlobalUtils(); publishSignalConfiguration(); _platformInjector = injector; const platform = injector.get(PlatformRef); runPlatformInitializers(injector); return platform; } /** * The goal of this function is to bootstrap a platform injector, * but avoid referencing `PlatformRef` class. * This function is needed for bootstrapping a Standalone Component. */ function createOrReusePlatformInjector(providers = []) { // If a platform injector already exists, it means that the platform // is already bootstrapped and no additional actions are required. if (_platformInjector) return _platformInjector; // Otherwise, setup a new platform injector and run platform initializers. const injector = createPlatformInjector(providers); _platformInjector = injector; publishDefaultGlobalUtils(); publishSignalConfiguration(); runPlatformInitializers(injector); return injector; } function runPlatformInitializers(injector) { const inits = injector.get(PLATFORM_INITIALIZER, null); inits?.forEach((init) => init()); } /** * Internal create application API that implements the core application creation logic and optional * bootstrap logic. * * Platforms (such as `platform-browser`) may require different set of application and platform * providers for an application to function correctly. As a result, platforms may use this function * internally and supply the necessary providers during the bootstrap, while exposing * platform-specific APIs as a part of their public API. * * @returns A promise that returns an `ApplicationRef` instance once resolved. */ function internalCreateApplication(config) { try { const { rootComponent, appProviders, platformProviders } = config; if ((typeof ngDevMode === 'undefined' || ngDevMode) && rootComponent !== undefined) { assertStandaloneComponentType(rootComponent); } const platformInjector = createOrReusePlatformInjector(platformProviders); // Create root application injector based on a set of providers configured at the platform // bootstrap level as well as providers passed to the bootstrap call by a user. const allAppProviders = [ provideZoneChangeDetection(), ...(appProviders || []), ]; const adapter = new EnvironmentNgModuleRefAdapter({ providers: allAppProviders, parent: platformInjector, debugName: (typeof ngDevMode === 'undefined' || ngDevMode) ? 'Environment Injector' : '', // We skip environment initializers because we need to run them inside the NgZone, which // happens after we get the NgZone instance from the Injector. runEnvironmentInitializers: false, }); const envInjector = adapter.injector; const ngZone = envInjector.get(NgZone); return ngZone.run(() => { envInjector.resolveInjectorInitializers(); const exceptionHandler = envInjector.get(ErrorHandler, null); if ((typeof ngDevMode === 'undefined' || ngDevMode) && !exceptionHandler) { throw new RuntimeError(402 /* RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP */, 'No `ErrorHandler` found in the Dependency Injection tree.'); } let onErrorSubscription; ngZone.runOutsideAngular(() => { onErrorSubscription = ngZone.onError.subscribe({ next: (error) => { exceptionHandler.handleError(error); } }); }); // If the whole platform is destroyed, invoke the `destroy` method // for all bootstrapped applications as well. const destroyListener = () => envInjector.destroy(); const onPlatformDestroyListeners = platformInjector.get(PLATFORM_DESTROY_LISTENERS); onPlatformDestroyListeners.add(destroyListener); envInjector.onDestroy(() => { onErrorSubscription.unsubscribe(); onPlatformDestroyListeners.delete(destroyListener); }); return _callAndReportToErrorHandler(exceptionHandler, ngZone, () => { const initStatus = envInjector.get(ApplicationInitStatus); initStatus.runInitializers(); return initStatus.donePromise.then(() => { const localeId = envInjector.get(LOCALE_ID, DEFAULT_LOCALE_ID); setLocaleId(localeId || DEFAULT_LOCALE_ID); const appRef = envInjector.get(ApplicationRef); if (rootComponent !== undefined) { appRef.bootstrap(rootComponent); } return appRef; }); }); }); } catch (e) { return Promise.reject(e); } } /** * Creates a factory for a platform. Can be used to provide or override `Providers` specific to * your application's runtime needs, such as `PLATFORM_INITIALIZER` and `PLATFORM_ID`. * @param parentPlatformFactory Another platform factory to modify. Allows you to compose factories * to build up configurations that might be required by different libraries or parts of the * application. * @param name Identifies the new platform factory. * @param providers A set of dependency providers for platforms created with the new factory. * * @publicApi */ function createPlatformFactory(parentPlatformFactory, name, providers = []) { const desc = `Platform: ${name}`; const marker = new InjectionToken(desc); return (extraProviders = []) => { let platform = getPlatform(); if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) { const platformProviders = [ ...providers, ...extraProviders, { provide: marker, useValue: true } ]; if (parentPlatformFactory) { parentPlatformFactory(platformProviders); } else { createPlatform(createPlatformInjector(platformProviders, desc)); } } return assertPlatform(marker); }; } /** * Checks that there is currently a platform that contains the given token as a provider. * * @publicApi */ function assertPlatform(requiredToken) { const platform = getPlatform(); if (!platform) { throw new RuntimeError(401 /* RuntimeErrorCode.PLATFORM_NOT_FOUND */, ngDevMode && 'No platform exists!'); } if ((typeof ngDevMode === 'undefined' || ngDevMode) && !platform.injector.get(requiredToken, null)) { throw new RuntimeError(400 /* RuntimeErrorCode.MULTIPLE_PLATFORMS */, 'A platform with a different configuration has been created. Please destroy it first.'); } return platform; } /** * Helper function to create an instance of a platform injector (that maintains the 'platform' * scope). */ function createPlatformInjector(providers = [], name) { return Injector.create({ name, providers: [ { provide: INJECTOR_SCOPE, useValue: 'platform' }, { provide: PLATFORM_DESTROY_LISTENERS, useValue: new Set([() => _platformInjector = null]) }, ...providers ], }); } /** * Destroys the current Angular platform and all Angular applications on the page. * Destroys all modules and listeners registered with the platform. * * @publicApi */ function destroyPlatform() { getPlatform()?.destroy(); } /** * Returns the current platform. * * @publicApi */ function getPlatform() { return _platformInjector?.get(PlatformRef) ?? null; } /** * The Angular platform is the entry point for Angular on a web page. * Each page has exactly one platform. Services (such as reflection) which are common * to every Angular application running on the page are bound in its scope. * A page's platform is initialized implicitly when a platform is created using a platform * factory such as `PlatformBrowser`, or explicitly by calling the `createPlatform()` function. * * @publicApi */ class PlatformRef { /** @internal */ constructor(_injector) { this._injector = _injector; this._modules = []; this._destroyListeners = []; this._destroyed = false; } /** * Creates an instance of an `@NgModule` for the given platform. * * @deprecated Passing NgModule factories as the `PlatformRef.bootstrapModuleFactory` function * argument is deprecated. Use the `PlatformRef.bootstrapModule` API instead. */ bootstrapModuleFactory(moduleFactory, options) { // Note: We need to create the NgZone _before_ we instantiate the module, // as instantiating the module creates some providers eagerly. // So we create a mini parent injector that just contains the new NgZone and // pass that as parent to the NgModuleFactory. const ngZone = getNgZone(options?.ngZone, getNgZoneOptions({ eventCoalescing: options?.ngZoneEventCoalescing, runCoalescing: options?.ngZoneRunCoalescing })); // Note: Create ngZoneInjector within ngZone.run so that all of the instantiated services are // created within the Angular zone // Do not try to replace ngZone.run with ApplicationRef#run because ApplicationRef would then be // created outside of the Angular zone. return ngZone.run(() => { const moduleRef = createNgModuleRefWithProviders(moduleFactory.moduleType, this.injector, internalProvideZoneChangeDetection(() => ngZone)); if ((typeof ngDevMode === 'undefined' || ngDevMode) && moduleRef.injector.get(PROVIDED_NG_ZONE, null) !== null) { throw new RuntimeError(207 /* RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT */, '`bootstrapModule` does not support `provideZoneChangeDetection`. Use `BootstrapOptions` instead.'); } const exceptionHandler = moduleRef.injector.get(ErrorHandler, null); if ((typeof ngDevMode === 'undefined' || ngDevMode) && exceptionHandler === null) { throw new RuntimeError(402 /* RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP */, 'No ErrorHandler. Is platform module (BrowserModule) included?'); } ngZone.runOutsideAngular(() => { const subscription = ngZone.onError.subscribe({ next: (error) => { exceptionHandler.handleError(error); } }); moduleRef.onDestroy(() => { remove(this._modules, moduleRef); subscription.unsubscribe(); }); }); return _callAndReportToErrorHandler(exceptionHandler, ngZone, () => { const initStatus = moduleRef.injector.get(ApplicationInitStatus); initStatus.runInitializers(); return initStatus.donePromise.then(() => { // If the `LOCALE_ID` provider is defined at bootstrap then we set the value for ivy const localeId = moduleRef.injector.get(LOCALE_ID, DEFAULT_LOCALE_ID); setLocaleId(localeId || DEFAULT_LOCALE_ID); this._moduleDoBootstrap(moduleRef); return moduleRef; }); }); }); } /** * Creates an instance of an `@NgModule` for a given platform. * * @usageNotes * ### Simple Example * * ```typescript * @NgModule({ * imports: [BrowserModule] * }) * class MyModule {} * * let moduleRef = platformBrowser().bootstrapModule(MyModule); * ``` * */ bootstrapModule(moduleType, compilerOptions = []) { const options = optionsReducer({}, compilerOptions); return compileNgModuleFactory(this.injector, options, moduleType) .then(moduleFactory => this.bootstrapModuleFactory(moduleFactory, options)); } _moduleDoBootstrap(moduleRef) { const appRef = moduleRef.injector.get(ApplicationRef); if (moduleRef._bootstrapComponents.length > 0) { moduleRef._bootstrapComponents.forEach(f => appRef.bootstrap(f)); } else if (moduleRef.instance.ngDoBootstrap) { moduleRef.instance.ngDoBootstrap(appRef); } else { throw new RuntimeError(-403 /* RuntimeErrorCode.BOOTSTRAP_COMPONENTS_NOT_FOUND */, ngDevMode && `The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, ` + `but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` + `Please define one of these.`); } this._modules.push(moduleRef); } /** * Registers a listener to be called when the platform is destroyed. */ onDestroy(callback) { this._destroyListeners.push(callback); } /** * Retrieves the platform {@link Injector}, which is the parent injector for * every Angular application on the page and provides singleton providers. */ get injector() { return this._injector; } /** * Destroys the current Angular platform and all Angular applications on the page. * Destroys all modules and listeners registered with the platform. */ destroy() { if (this._destroyed) { throw new RuntimeError(404 /* RuntimeErrorCode.PLATFORM_ALREADY_DESTROYED */, ngDevMode && 'The platform has already been destroyed!'); } this._modules.slice().forEach(module => module.destroy()); this._destroyListeners.forEach(listener => listener()); const destroyListeners = this._injector.get(PLATFORM_DESTROY_LISTENERS, null); if (destroyListeners) { destroyListeners.forEach(listener => listener()); destroyListeners.clear(); } this._destroyed = true; } /** * Indicates whether this instance was destroyed. */ get destroyed() { return this._destroyed; } static { this.ɵfac = function PlatformRef_Factory(t) { return new (t || PlatformRef)(ɵɵinject(Injector)); }; } static { this.ɵprov = /*@__PURE__*/ ɵɵdefineInjectable({ token: PlatformRef, factory: PlatformRef.ɵfac, providedIn: 'platform' }); } } exports('PlatformRef', PlatformRef); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(PlatformRef, [{ type: Injectable, args: [{ providedIn: 'platform' }] }], function () { return [{ type: Injector }]; }, null); })(); // Transforms a set of `BootstrapOptions` (supported by the NgModule-based bootstrap APIs) -> // `NgZoneOptions` that are recognized by the NgZone constructor. Passing no options will result in // a set of default options returned. function getNgZoneOptions(options) { return { enableLongStackTrace: typeof ngDevMode === 'undefined' ? false : !!ngDevMode, shouldCoalesceEventChangeDetection: options?.eventCoalescing ?? false, shouldCoalesceRunChangeDetection: options?.runCoalescing ?? false, }; } function getNgZone(ngZoneToUse = 'zone.js', options) { if (ngZoneToUse === 'noop') { return new NoopNgZone(); } if (ngZoneToUse === 'zone.js') { return new NgZone(options); } return ngZoneToUse; } function _callAndReportToErrorHandler(errorHandler, ngZone, callback) { try { const result = callback(); if (isPromise(result)) { return result.catch((e) => { ngZone.runOutsideAngular(() => errorHandler.handleError(e)); // rethrow as the exception handler might not do it throw e; }); } return result; } catch (e) { ngZone.runOutsideAngular(() => errorHandler.handleError(e)); // rethrow as the exception handler might not do it throw e; } } function optionsReducer(dst, objs) { if (Array.isArray(objs)) { return objs.reduce(optionsReducer, dst); } return { ...dst, ...objs }; } /** * A reference to an Angular application running on a page. * * @usageNotes * {@a is-stable-examples} * ### isStable examples and caveats * * Note two important points about `isStable`, demonstrated in the examples below: * - the application will never be stable if you start any kind * of recurrent asynchronous task when the application starts * (for example for a polling process, started with a `setInterval`, a `setTimeout` * or using RxJS operators like `interval`); * - the `isStable` Observable runs outside of the Angular zone. * * Let's imagine that you start a recurrent task * (here incrementing a counter, using RxJS `interval`), * and at the same time subscribe to `isStable`. * * ``` * constructor(appRef: ApplicationRef) { * appRef.isStable.pipe( * filter(stable => stable) * ).subscribe(() => console.log('App is stable now'); * interval(1000).subscribe(counter => console.log(counter)); * } * ``` * In this example, `isStable` will never emit `true`, * and the trace "App is stable now" will never get logged. * * If you want to execute something when the app is stable, * you have to wait for the application to be stable * before starting your polling process. * * ``` * constructor(appRef: ApplicationRef) { * appRef.isStable.pipe( * first(stable => stable), * tap(stable => console.log('App is stable now')), * switchMap(() => interval(1000)) * ).subscribe(counter => console.log(counter)); * } * ``` * In this example, the trace "App is stable now" will be logged * and then the counter starts incrementing every second. * * Note also that this Observable runs outside of the Angular zone, * which means that the code in the subscription * to this Observable will not trigger the change detection. * * Let's imagine that instead of logging the counter value, * you update a field of your component * and display it in its template. * * ``` * constructor(appRef: ApplicationRef) { * appRef.isStable.pipe( * first(stable => stable), * switchMap(() => interval(1000)) * ).subscribe(counter => this.value = counter); * } * ``` * As the `isStable` Observable runs outside the zone, * the `value` field will be updated properly, * but the template will not be refreshed! * * You'll have to manually trigger the change detection to update the template. * * ``` * constructor(appRef: ApplicationRef, cd: ChangeDetectorRef) { * appRef.isStable.pipe( * first(stable => stable), * switchMap(() => interval(1000)) * ).subscribe(counter => { * this.value = counter; * cd.detectChanges(); * }); * } * ``` * * Or make the subscription callback run inside the zone. * * ``` * constructor(appRef: ApplicationRef, zone: NgZone) { * appRef.isStable.pipe( * first(stable => stable), * switchMap(() => interval(1000)) * ).subscribe(counter => zone.run(() => this.value = counter)); * } * ``` * * @publicApi */ class ApplicationRef { constructor() { /** @internal */ this._bootstrapListeners = []; this._runningTick = false; this._destroyed = false; this._destroyListeners = []; /** @internal */ this._views = []; this.internalErrorHandler = inject(INTERNAL_APPLICATION_ERROR_HANDLER); this.zoneIsStable = inject(ZONE_IS_STABLE_OBSERVABLE); /** * Get a list of component types registered to this application. * This list is populated even before the component is created. */ this.componentTypes = []; /** * Get a list of components registered to this application. */ this.components = []; /** * Returns an Observable that indicates when the application is stable or unstable. */ this.isStable = inject(InitialRenderPendingTasks) .hasPendingTasks.pipe(switchMap(hasPendingTasks => hasPendingTasks ? of(false) : this.zoneIsStable), distinctUntilChanged(), share()); this._injector = inject(EnvironmentInjector); } /** * Indicates whether this instance was destroyed. */ get destroyed() { return this._destroyed; } /** * The `EnvironmentInjector` used to create this application. */ get injector() { return this._injector; } /** * Bootstrap a component onto the element identified by its selector or, optionally, to a * specified element. * * @usageNotes * ### Bootstrap process * * When bootstrapping a component, Angular mounts it onto a target DOM element * and kicks off automatic change detection. The target DOM element can be * provided using the `rootSelectorOrNode` argument. * * If the target DOM element is not provided, Angular tries to find one on a page * using the `selector` of the component that is being bootstrapped * (first matched element is used). * * ### Example * * Generally, we define the component to bootstrap in the `bootstrap` array of `NgModule`, * but it requires us to know the component while writing the application code. * * Imagine a situation where we have to wait for an API call to decide about the component to * bootstrap. We can use the `ngDoBootstrap` hook of the `NgModule` and call this method to * dynamically bootstrap a component. * * {@example core/ts/platform/platform.ts region='componentSelector'} * * Optionally, a component can be mounted onto a DOM element that does not match the * selector of the bootstrapped component. * * In the following example, we are providing a CSS selector to match the target element. * * {@example core/ts/platform/platform.ts region='cssSelector'} * * While in this example, we are providing reference to a DOM node. * * {@example core/ts/platform/platform.ts region='domNode'} */ bootstrap(componentOrFactory, rootSelectorOrNode) { (typeof ngDevMode === 'undefined' || ngDevMode) && this.warnIfDestroyed(); const isComponentFactory = componentOrFactory instanceof ComponentFactory$1; const initStatus = this._injector.get(ApplicationInitStatus); if (!initStatus.done) { const standalone = !isComponentFactory && isStandalone(componentOrFactory); const errorMessage = 'Cannot bootstrap as there are still asynchronous initializers running.' + (standalone ? '' : ' Bootstrap components in the `ngDoBootstrap` method of the root module.'); throw new RuntimeError(405 /* RuntimeErrorCode.ASYNC_INITIALIZERS_STILL_RUNNING */, (typeof ngDevMode === 'undefined' || ngDevMode) && errorMessage); } let componentFactory; if (isComponentFactory) { componentFactory = componentOrFactory; } else { const resolver = this._injector.get(ComponentFactoryResolver$1); componentFactory = resolver.resolveComponentFactory(componentOrFactory); } this.componentTypes.push(componentFactory.componentType); // Create a factory associated with the current module if it's not bound to some other const ngModule = isBoundToModule(componentFactory) ? undefined : this._injector.get(NgModuleRef$1); const selectorOrNode = rootSelectorOrNode || componentFactory.selector; const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule); const nativeElement = compRef.location.nativeElement; const testability = compRef.injector.get(TESTABILITY, null); testability?.registerApplication(nativeElement); compRef.onDestroy(() => { this.detachView(compRef.hostView); remove(this.components, compRef); testability?.unregisterApplication(nativeElement); }); this._loadComponent(compRef); if (typeof ngDevMode === 'undefined' || ngDevMode) { const _console = this._injector.get(Console); _console.log(`Angular is running in development mode.`); } return compRef; } /** * Invoke this method to explicitly process change detection and its side-effects. * * In development mode, `tick()` also performs a second change detection cycle to ensure that no * further changes are detected. If additional changes are picked up during this second cycle, * bindings in the app have side-effects that cannot be resolved in a single change detection * pass. * In this case, Angular throws an error, since an Angular application can only have one change * detection pass during which all change detection must complete. */ tick() { (typeof ngDevMode === 'undefined' || ngDevMode) && this.warnIfDestroyed(); if (this._runningTick) { throw new RuntimeError(101 /* RuntimeErrorCode.RECURSIVE_APPLICATION_REF_TICK */, ngDevMode && 'ApplicationRef.tick is called recursively'); } try { this._runningTick = true; for (let view of this._views) { view.detectChanges(); } if (typeof ngDevMode === 'undefined' || ngDevMode) { for (let view of this._views) { view.checkNoChanges(); } } } catch (e) { // Attention: Don't rethrow as it could cancel subscriptions to Observables! this.internalErrorHandler(e); } finally { this._runningTick = false; } } /** * Attaches a view so that it will be dirty checked. * The view will be automatically detached when it is destroyed. * This will throw if the view is already attached to a ViewContainer. */ attachView(viewRef) { (typeof ngDevMode === 'undefined' || ngDevMode) && this.warnIfDestroyed(); const view = viewRef; this._views.push(view); view.attachToAppRef(this); } /** * Detaches a view from dirty checking again. */ detachView(viewRef) { (typeof ngDevMode === 'undefined' || ngDevMode) && this.warnIfDestroyed(); const view = viewRef; remove(this._views, view); view.detachFromAppRef(); } _loadComponent(componentRef) { this.attachView(componentRef.hostView); this.tick(); this.components.push(componentRef); // Get the listeners lazily to prevent DI cycles. const listeners = this._injector.get(APP_BOOTSTRAP_LISTENER, []); if (ngDevMode && !Array.isArray(listeners)) { throw new RuntimeError(-209 /* RuntimeErrorCode.INVALID_MULTI_PROVIDER */, 'Unexpected type of the `APP_BOOTSTRAP_LISTENER` token value ' + `(expected an array, but got ${typeof listeners}). ` + 'Please check that the `APP_BOOTSTRAP_LISTENER` token is configured as a ' + '`multi: true` provider.'); } listeners.push(...this._bootstrapListeners); listeners.forEach((listener) => listener(componentRef)); } /** @internal */ ngOnDestroy() { if (this._destroyed) return; try { // Call all the lifecycle hooks. this._destroyListeners.forEach(listener => listener()); // Destroy all registered views. this._views.slice().forEach((view) => view.destroy()); } finally { // Indicate that this instance is destroyed. this._destroyed = true; // Release all references. this._views = []; this._bootstrapListeners = []; this._destroyListeners = []; } } /** * Registers a listener to be called when an instance is destroyed. * * @param callback A callback function to add as a listener. * @returns A function which unregisters a listener. */ onDestroy(callback) { (typeof ngDevMode === 'undefined' || ngDevMode) && this.warnIfDestroyed(); this._destroyListeners.push(callback); return () => remove(this._destroyListeners, callback); } /** * Destroys an Angular application represented by this `ApplicationRef`. Calling this function * will destroy the associated environment injectors as well as all the bootstrapped components * with their views. */ destroy() { if (this._destroyed) { throw new RuntimeError(406 /* RuntimeErrorCode.APPLICATION_REF_ALREADY_DESTROYED */, ngDevMode && 'This instance of the `ApplicationRef` has already been destroyed.'); } const injector = this._injector; // Check that this injector instance supports destroy operation. if (injector.destroy && !injector.destroyed) { // Destroying an underlying injector will trigger the `ngOnDestroy` lifecycle // hook, which invokes the remaining cleanup actions. injector.destroy(); } } /** * Returns the number of attached views. */ get viewCount() { return this._views.length; } warnIfDestroyed() { if ((typeof ngDevMode === 'undefined' || ngDevMode) && this._destroyed) { console.warn(formatRuntimeError(406 /* RuntimeErrorCode.APPLICATION_REF_ALREADY_DESTROYED */, 'This instance of the `ApplicationRef` has already been destroyed.')); } } static { this.ɵfac = function ApplicationRef_Factory(t) { return new (t || ApplicationRef)(); }; } static { this.ɵprov = /*@__PURE__*/ ɵɵdefineInjectable({ token: ApplicationRef, factory: ApplicationRef.ɵfac, providedIn: 'root' }); } } exports('ApplicationRef', ApplicationRef); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ApplicationRef, [{ type: Injectable, args: [{ providedIn: 'root' }] }], null, null); })(); function remove(list, el) { const index = list.indexOf(el); if (index > -1) { list.splice(index, 1); } } function _lastDefined(args) { for (let i = args.length - 1; i >= 0; i--) { if (args[i] !== undefined) { return args[i]; } } return undefined; } /** * `InjectionToken` used to configure how to call the `ErrorHandler`. * * `NgZone` is provided by default today so the default (and only) implementation for this * is calling `ErrorHandler.handleError` outside of the Angular zone. */ const INTERNAL_APPLICATION_ERROR_HANDLER = new InjectionToken((typeof ngDevMode === 'undefined' || ngDevMode) ? 'internal error handler' : '', { providedIn: 'root', factory: () => { const userErrorHandler = inject(ErrorHandler); return userErrorHandler.handleError.bind(undefined); } }); function ngZoneApplicationErrorHandlerFactory() { const zone = inject(NgZone); const userErrorHandler = inject(ErrorHandler); return (e) => zone.runOutsideAngular(() => userErrorHandler.handleError(e)); } class NgZoneChangeDetectionScheduler { constructor() { this.zone = inject(NgZone); this.applicationRef = inject(ApplicationRef); } initialize() { if (this._onMicrotaskEmptySubscription) { return; } this._onMicrotaskEmptySubscription = this.zone.onMicrotaskEmpty.subscribe({ next: () => { this.zone.run(() => { this.applicationRef.tick(); }); } }); } ngOnDestroy() { this._onMicrotaskEmptySubscription?.unsubscribe(); } static { this.ɵfac = function NgZoneChangeDetectionScheduler_Factory(t) { return new (t || NgZoneChangeDetectionScheduler)(); }; } static { this.ɵprov = /*@__PURE__*/ ɵɵdefineInjectable({ token: NgZoneChangeDetectionScheduler, factory: NgZoneChangeDetectionScheduler.ɵfac, providedIn: 'root' }); } } (function () { (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(NgZoneChangeDetectionScheduler, [{ type: Injectable, args: [{ providedIn: 'root' }] }], null, null); })(); /** * Internal token used to verify that `provideZoneChangeDetection` is not used * with the bootstrapModule API. */ const PROVIDED_NG_ZONE = new InjectionToken((typeof ngDevMode === 'undefined' || ngDevMode) ? 'provideZoneChangeDetection token' : ''); function internalProvideZoneChangeDetection(ngZoneFactory) { return [ { provide: NgZone, useFactory: ngZoneFactory }, { provide: ENVIRONMENT_INITIALIZER, multi: true, useFactory: () => { const ngZoneChangeDetectionScheduler = inject(NgZoneChangeDetectionScheduler, { optional: true }); if ((typeof ngDevMode === 'undefined' || ngDevMode) && ngZoneChangeDetectionScheduler === null) { throw new RuntimeError(402 /* RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP */, `A required Injectable was not found in the dependency injection tree. ` + 'If you are bootstrapping an NgModule, make sure that the `BrowserModule` is imported.'); } return () => ngZoneChangeDetectionScheduler.initialize(); }, }, { provide: INTERNAL_APPLICATION_ERROR_HANDLER, useFactory: ngZoneApplicationErrorHandlerFactory }, { provide: ZONE_IS_STABLE_OBSERVABLE, useFactory: isStableFactory }, ]; } /** * Provides `NgZone`-based change detection for the application bootstrapped using * `bootstrapApplication`. * * `NgZone` is already provided in applications by default. This provider allows you to configure * options like `eventCoalescing` in the `NgZone`. * This provider is not available for `platformBrowser().bootstrapModule`, which uses * `BootstrapOptions` instead. * * @usageNotes * ```typescript= * bootstrapApplication(MyApp, {providers: [ * provideZoneChangeDetection({eventCoalescing: true}), * ]}); * ``` * * @publicApi * @see bootstrapApplication * @see NgZoneOptions */ function provideZoneChangeDetection(options) { const zoneProviders = internalProvideZoneChangeDetection(() => new NgZone(getNgZoneOptions(options))); return makeEnvironmentProviders([ (typeof ngDevMode === 'undefined' || ngDevMode) ? { provide: PROVIDED_NG_ZONE, useValue: true } : [], zoneProviders, ]); } /** * Returns whether Angular is in development mode. * * By default, this is true, unless `enableProdMode` is invoked prior to calling this method or the * application is built using the Angular CLI with the `optimization` option. * @see {@link cli/build ng build} * * @publicApi */ function isDevMode() { return typeof ngDevMode === 'undefined' || !!ngDevMode; } /** * Disable Angular's development mode, which turns off assertions and other * checks within the framework. * * One important assertion this disables verifies that a change detection pass * does not result in additional changes to any bindings (also known as * unidirectional data flow). * * Using this method is discouraged as the Angular CLI will set production mode when using the * `optimization` option. * @see {@link cli/build ng build} * * @publicApi */ function enableProdMode() { // The below check is there so when ngDevMode is set via terser // `global['ngDevMode'] = false;` is also dropped. if (typeof ngDevMode === 'undefined' || ngDevMode) { _global['ngDevMode'] = false; } } // Public API for Zone // Public API for render /** * Returns the NgModuleFactory with the given id (specified using [@NgModule.id * field](api/core/NgModule#id)), if it exists and has been loaded. Factories for NgModules that do * not specify an `id` cannot be retrieved. Throws if an NgModule cannot be found. * @publicApi * @deprecated Use `getNgModuleById` instead. */ function getModuleFactory(id) { const type = getRegisteredNgModuleType(id); if (!type) throw noModuleError(id); return new NgModuleFactory(type); } /** * Returns the NgModule class with the given id (specified using [@NgModule.id * field](api/core/NgModule#id)), if it exists and has been loaded. Classes for NgModules that do * not specify an `id` cannot be retrieved. Throws if an NgModule cannot be found. * @publicApi */ function getNgModuleById(id) { const type = getRegisteredNgModuleType(id); if (!type) throw noModuleError(id); return type; } function noModuleError(id) { return new Error(`No module with ID ${id} loaded`); } /** * Base class that provides change detection functionality. * A change-detection tree collects all views that are to be checked for changes. * Use the methods to add and remove views from the tree, initiate change-detection, * and explicitly mark views as _dirty_, meaning that they have changed and need to be re-rendered. * * @see [Using change detection hooks](guide/lifecycle-hooks#using-change-detection-hooks) * @see [Defining custom change detection](guide/lifecycle-hooks#defining-custom-change-detection) * * @usageNotes * * The following examples demonstrate how to modify default change-detection behavior * to perform explicit detection when needed. * * ### Use `markForCheck()` with `CheckOnce` strategy * * The following example sets the `OnPush` change-detection strategy for a component * (`CheckOnce`, rather than the default `CheckAlways`), then forces a second check * after an interval. * * * * ### Detach change detector to limit how often check occurs * * The following example defines a component with a large list of read-only data * that is expected to change constantly, many times per second. * To improve performance, we want to check and update the list * less often than the changes actually occur. To do that, we detach * the component's change detector and perform an explicit local check every five seconds. * * * * * ### Reattaching a detached component * * The following example creates a component displaying live data. * The component detaches its change detector from the main change detector tree * when the `live` property is set to false, and reattaches it when the property * becomes true. * * * * @publicApi */ class ChangeDetectorRef { /** * @internal * @nocollapse */ static { this.__NG_ELEMENT_ID__ = injectChangeDetectorRef; } } exports('ChangeDetectorRef', ChangeDetectorRef); /** Returns a ChangeDetectorRef (a.k.a. a ViewRef) */ function injectChangeDetectorRef(flags) { return createViewRef(getCurrentTNode(), getLView(), (flags & 16 /* InternalInjectFlags.ForPipe */) === 16 /* InternalInjectFlags.ForPipe */); } /** * Creates a ViewRef and stores it on the injector as ChangeDetectorRef (public alias). * * @param tNode The node that is requesting a ChangeDetectorRef * @param lView The view to which the node belongs * @param isPipe Whether the view is being injected into a pipe. * @returns The ChangeDetectorRef to use */ function createViewRef(tNode, lView, isPipe) { if (isComponentHost(tNode) && !isPipe) { // The LView represents the location where the component is declared. // Instead we want the LView for the component View and so we need to look it up. const componentView = getComponentLViewByIndex(tNode.index, lView); // look down return new ViewRef$1(componentView, componentView); } else if (tNode.type & (3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */ | 32 /* TNodeType.Icu */)) { // The LView represents the location where the injection is requested from. // We need to locate the containing LView (in case where the `lView` is an embedded view) const hostComponentView = lView[DECLARATION_COMPONENT_VIEW]; // look up return new ViewRef$1(hostComponentView, lView); } return null; } /** * Represents an Angular [view](guide/glossary#view "Definition"). * * @see {@link ChangeDetectorRef#usage-notes Change detection usage} * * @publicApi */ class ViewRef extends ChangeDetectorRef { } exports('ViewRef', ViewRef); /** * Represents an Angular [view](guide/glossary#view) in a view container. * An [embedded view](guide/glossary#view-hierarchy) can be referenced from a component * other than the hosting component whose template defines it, or it can be defined * independently by a `TemplateRef`. * * Properties of elements in a view can change, but the structure (number and order) of elements in * a view cannot. Change the structure of elements by inserting, moving, or * removing nested views in a view container. * * @see `ViewContainerRef` * * @usageNotes * * The following template breaks down into two separate `TemplateRef` instances, * an outer one and an inner one. * * ``` * Count: {{items.length}} *
    *
  • {{item}}
  • *
* ``` * * This is the outer `TemplateRef`: * * ``` * Count: {{items.length}} *
    * *
* ``` * * This is the inner `TemplateRef`: * * ``` *
  • {{item}}
  • * ``` * * The outer and inner `TemplateRef` instances are assembled into views as follows: * * ``` * * Count: 2 *
      * *
    • first
    • *
    • second
    • *
    * * ``` * @publicApi */ class EmbeddedViewRef extends ViewRef { } exports('EmbeddedViewRef', EmbeddedViewRef); /** * @publicApi */ class DebugEventListener { constructor(name, callback) { this.name = name; this.callback = callback; } } exports('DebugEventListener', DebugEventListener); /** * @publicApi */ function asNativeElements(debugEls) { return debugEls.map((el) => el.nativeElement); } /** * @publicApi */ class DebugNode { constructor(nativeNode) { this.nativeNode = nativeNode; } /** * The `DebugElement` parent. Will be `null` if this is the root element. */ get parent() { const parent = this.nativeNode.parentNode; return parent ? new DebugElement(parent) : null; } /** * The host dependency injector. For example, the root element's component instance injector. */ get injector() { return getInjector(this.nativeNode); } /** * The element's own component instance, if it has one. */ get componentInstance() { const nativeElement = this.nativeNode; return nativeElement && (getComponent(nativeElement) || getOwningComponent(nativeElement)); } /** * An object that provides parent context for this element. Often an ancestor component instance * that governs this element. * * When an element is repeated within *ngFor, the context is an `NgForOf` whose `$implicit` * property is the value of the row instance value. For example, the `hero` in `*ngFor="let hero * of heroes"`. */ get context() { return getComponent(this.nativeNode) || getContext(this.nativeNode); } /** * The callbacks attached to the component's @Output properties and/or the element's event * properties. */ get listeners() { return getListeners(this.nativeNode).filter(listener => listener.type === 'dom'); } /** * Dictionary of objects associated with template local variables (e.g. #foo), keyed by the local * variable name. */ get references() { return getLocalRefs(this.nativeNode); } /** * This component's injector lookup tokens. Includes the component itself plus the tokens that the * component lists in its providers metadata. */ get providerTokens() { return getInjectionTokens(this.nativeNode); } } exports('DebugNode', DebugNode); /** * @publicApi * * @see [Component testing scenarios](guide/testing-components-scenarios) * @see [Basics of testing components](guide/testing-components-basics) * @see [Testing utility APIs](guide/testing-utility-apis) */ class DebugElement extends DebugNode { constructor(nativeNode) { ngDevMode && assertDomNode(nativeNode); super(nativeNode); } /** * The underlying DOM element at the root of the component. */ get nativeElement() { return this.nativeNode.nodeType == Node.ELEMENT_NODE ? this.nativeNode : null; } /** * The element tag name, if it is an element. */ get name() { const context = getLContext(this.nativeNode); const lView = context ? context.lView : null; if (lView !== null) { const tData = lView[TVIEW].data; const tNode = tData[context.nodeIndex]; return tNode.value; } else { return this.nativeNode.nodeName; } } /** * Gets a map of property names to property values for an element. * * This map includes: * - Regular property bindings (e.g. `[id]="id"`) * - Host property bindings (e.g. `host: { '[id]': "id" }`) * - Interpolated property bindings (e.g. `id="{{ value }}") * * It does not include: * - input property bindings (e.g. `[myCustomInput]="value"`) * - attribute bindings (e.g. `[attr.role]="menu"`) */ get properties() { const context = getLContext(this.nativeNode); const lView = context ? context.lView : null; if (lView === null) { return {}; } const tData = lView[TVIEW].data; const tNode = tData[context.nodeIndex]; const properties = {}; // Collect properties from the DOM. copyDomProperties(this.nativeElement, properties); // Collect properties from the bindings. This is needed for animation renderer which has // synthetic properties which don't get reflected into the DOM. collectPropertyBindings(properties, tNode, lView, tData); return properties; } /** * A map of attribute names to attribute values for an element. */ get attributes() { const attributes = {}; const element = this.nativeElement; if (!element) { return attributes; } const context = getLContext(element); const lView = context ? context.lView : null; if (lView === null) { return {}; } const tNodeAttrs = lView[TVIEW].data[context.nodeIndex].attrs; const lowercaseTNodeAttrs = []; // For debug nodes we take the element's attribute directly from the DOM since it allows us // to account for ones that weren't set via bindings (e.g. ViewEngine keeps track of the ones // that are set through `Renderer2`). The problem is that the browser will lowercase all names, // however since we have the attributes already on the TNode, we can preserve the case by going // through them once, adding them to the `attributes` map and putting their lower-cased name // into an array. Afterwards when we're going through the native DOM attributes, we can check // whether we haven't run into an attribute already through the TNode. if (tNodeAttrs) { let i = 0; while (i < tNodeAttrs.length) { const attrName = tNodeAttrs[i]; // Stop as soon as we hit a marker. We only care about the regular attributes. Everything // else will be handled below when we read the final attributes off the DOM. if (typeof attrName !== 'string') break; const attrValue = tNodeAttrs[i + 1]; attributes[attrName] = attrValue; lowercaseTNodeAttrs.push(attrName.toLowerCase()); i += 2; } } for (const attr of element.attributes) { // Make sure that we don't assign the same attribute both in its // case-sensitive form and the lower-cased one from the browser. if (!lowercaseTNodeAttrs.includes(attr.name)) { attributes[attr.name] = attr.value; } } return attributes; } /** * The inline styles of the DOM element. * * Will be `null` if there is no `style` property on the underlying DOM element. * * @see [ElementCSSInlineStyle](https://developer.mozilla.org/en-US/docs/Web/API/ElementCSSInlineStyle/style) */ get styles() { if (this.nativeElement && this.nativeElement.style) { return this.nativeElement.style; } return {}; } /** * A map containing the class names on the element as keys. * * This map is derived from the `className` property of the DOM element. * * Note: The values of this object will always be `true`. The class key will not appear in the KV * object if it does not exist on the element. * * @see [Element.className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) */ get classes() { const result = {}; const element = this.nativeElement; // SVG elements return an `SVGAnimatedString` instead of a plain string for the `className`. const className = element.className; const classes = typeof className !== 'string' ? className.baseVal.split(' ') : className.split(' '); classes.forEach((value) => result[value] = true); return result; } /** * The `childNodes` of the DOM element as a `DebugNode` array. * * @see [Node.childNodes](https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes) */ get childNodes() { const childNodes = this.nativeNode.childNodes; const children = []; for (let i = 0; i < childNodes.length; i++) { const element = childNodes[i]; children.push(getDebugNode(element)); } return children; } /** * The immediate `DebugElement` children. Walk the tree by descending through `children`. */ get children() { const nativeElement = this.nativeElement; if (!nativeElement) return []; const childNodes = nativeElement.children; const children = []; for (let i = 0; i < childNodes.length; i++) { const element = childNodes[i]; children.push(getDebugNode(element)); } return children; } /** * @returns the first `DebugElement` that matches the predicate at any depth in the subtree. */ query(predicate) { const results = this.queryAll(predicate); return results[0] || null; } /** * @returns All `DebugElement` matches for the predicate at any depth in the subtree. */ queryAll(predicate) { const matches = []; _queryAll(this, predicate, matches, true); return matches; } /** * @returns All `DebugNode` matches for the predicate at any depth in the subtree. */ queryAllNodes(predicate) { const matches = []; _queryAll(this, predicate, matches, false); return matches; } /** * Triggers the event by its name if there is a corresponding listener in the element's * `listeners` collection. * * If the event lacks a listener or there's some other problem, consider * calling `nativeElement.dispatchEvent(eventObject)`. * * @param eventName The name of the event to trigger * @param eventObj The _event object_ expected by the handler * * @see [Testing components scenarios](guide/testing-components-scenarios#trigger-event-handler) */ triggerEventHandler(eventName, eventObj) { const node = this.nativeNode; const invokedListeners = []; this.listeners.forEach(listener => { if (listener.name === eventName) { const callback = listener.callback; callback.call(node, eventObj); invokedListeners.push(callback); } }); // We need to check whether `eventListeners` exists, because it's something // that Zone.js only adds to `EventTarget` in browser environments. if (typeof node.eventListeners === 'function') { // Note that in Ivy we wrap event listeners with a call to `event.preventDefault` in some // cases. We use '__ngUnwrap__' as a special token that gives us access to the actual event // listener. node.eventListeners(eventName).forEach((listener) => { // In order to ensure that we can detect the special __ngUnwrap__ token described above, we // use `toString` on the listener and see if it contains the token. We use this approach to // ensure that it still worked with compiled code since it cannot remove or rename string // literals. We also considered using a special function name (i.e. if(listener.name === // special)) but that was more cumbersome and we were also concerned the compiled code could // strip the name, turning the condition in to ("" === "") and always returning true. if (listener.toString().indexOf('__ngUnwrap__') !== -1) { const unwrappedListener = listener('__ngUnwrap__'); return invokedListeners.indexOf(unwrappedListener) === -1 && unwrappedListener.call(node, eventObj); } }); } } } exports('DebugElement', DebugElement); function copyDomProperties(element, properties) { if (element) { // Skip own properties (as those are patched) let obj = Object.getPrototypeOf(element); const NodePrototype = Node.prototype; while (obj !== null && obj !== NodePrototype) { const descriptors = Object.getOwnPropertyDescriptors(obj); for (let key in descriptors) { if (!key.startsWith('__') && !key.startsWith('on')) { // don't include properties starting with `__` and `on`. // `__` are patched values which should not be included. // `on` are listeners which also should not be included. const value = element[key]; if (isPrimitiveValue(value)) { properties[key] = value; } } } obj = Object.getPrototypeOf(obj); } } } function isPrimitiveValue(value) { return typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number' || value === null; } function _queryAll(parentElement, predicate, matches, elementsOnly) { const context = getLContext(parentElement.nativeNode); const lView = context ? context.lView : null; if (lView !== null) { const parentTNode = lView[TVIEW].data[context.nodeIndex]; _queryNodeChildren(parentTNode, lView, predicate, matches, elementsOnly, parentElement.nativeNode); } else { // If the context is null, then `parentElement` was either created with Renderer2 or native DOM // APIs. _queryNativeNodeDescendants(parentElement.nativeNode, predicate, matches, elementsOnly); } } /** * Recursively match the current TNode against the predicate, and goes on with the next ones. * * @param tNode the current TNode * @param lView the LView of this TNode * @param predicate the predicate to match * @param matches the list of positive matches * @param elementsOnly whether only elements should be searched * @param rootNativeNode the root native node on which predicate should not be matched */ function _queryNodeChildren(tNode, lView, predicate, matches, elementsOnly, rootNativeNode) { ngDevMode && assertTNodeForLView(tNode, lView); const nativeNode = getNativeByTNodeOrNull(tNode, lView); // For each type of TNode, specific logic is executed. if (tNode.type & (3 /* TNodeType.AnyRNode */ | 8 /* TNodeType.ElementContainer */)) { // Case 1: the TNode is an element // The native node has to be checked. _addQueryMatch(nativeNode, predicate, matches, elementsOnly, rootNativeNode); if (isComponentHost(tNode)) { // If the element is the host of a component, then all nodes in its view have to be processed. // Note: the component's content (tNode.child) will be processed from the insertion points. const componentView = getComponentLViewByIndex(tNode.index, lView); if (componentView && componentView[TVIEW].firstChild) { _queryNodeChildren(componentView[TVIEW].firstChild, componentView, predicate, matches, elementsOnly, rootNativeNode); } } else { if (tNode.child) { // Otherwise, its children have to be processed. _queryNodeChildren(tNode.child, lView, predicate, matches, elementsOnly, rootNativeNode); } // We also have to query the DOM directly in order to catch elements inserted through // Renderer2. Note that this is __not__ optimal, because we're walking similar trees multiple // times. ViewEngine could do it more efficiently, because all the insertions go through // Renderer2, however that's not the case in Ivy. This approach is being used because: // 1. Matching the ViewEngine behavior would mean potentially introducing a dependency // from `Renderer2` to Ivy which could bring Ivy code into ViewEngine. // 2. It allows us to capture nodes that were inserted directly via the DOM. nativeNode && _queryNativeNodeDescendants(nativeNode, predicate, matches, elementsOnly); } // In all cases, if a dynamic container exists for this node, each view inside it has to be // processed. const nodeOrContainer = lView[tNode.index]; if (isLContainer(nodeOrContainer)) { _queryNodeChildrenInContainer(nodeOrContainer, predicate, matches, elementsOnly, rootNativeNode); } } else if (tNode.type & 4 /* TNodeType.Container */) { // Case 2: the TNode is a container // The native node has to be checked. const lContainer = lView[tNode.index]; _addQueryMatch(lContainer[NATIVE], predicate, matches, elementsOnly, rootNativeNode); // Each view inside the container has to be processed. _queryNodeChildrenInContainer(lContainer, predicate, matches, elementsOnly, rootNativeNode); } else if (tNode.type & 16 /* TNodeType.Projection */) { // Case 3: the TNode is a projection insertion point (i.e. a ). // The nodes projected at this location all need to be processed. const componentView = lView[DECLARATION_COMPONENT_VIEW]; const componentHost = componentView[T_HOST]; const head = componentHost.projection[tNode.projection]; if (Array.isArray(head)) { for (let nativeNode of head) { _addQueryMatch(nativeNode, predicate, matches, elementsOnly, rootNativeNode); } } else if (head) { const nextLView = componentView[PARENT]; const nextTNode = nextLView[TVIEW].data[head.index]; _queryNodeChildren(nextTNode, nextLView, predicate, matches, elementsOnly, rootNativeNode); } } else if (tNode.child) { // Case 4: the TNode is a view. _queryNodeChildren(tNode.child, lView, predicate, matches, elementsOnly, rootNativeNode); } // We don't want to go to the next sibling of the root node. if (rootNativeNode !== nativeNode) { // To determine the next node to be processed, we need to use the next or the projectionNext // link, depending on whether the current node has been projected. const nextTNode = (tNode.flags & 2 /* TNodeFlags.isProjected */) ? tNode.projectionNext : tNode.next; if (nextTNode) { _queryNodeChildren(nextTNode, lView, predicate, matches, elementsOnly, rootNativeNode); } } } /** * Process all TNodes in a given container. * * @param lContainer the container to be processed * @param predicate the predicate to match * @param matches the list of positive matches * @param elementsOnly whether only elements should be searched * @param rootNativeNode the root native node on which predicate should not be matched */ function _queryNodeChildrenInContainer(lContainer, predicate, matches, elementsOnly, rootNativeNode) { for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) { const childView = lContainer[i]; const firstChild = childView[TVIEW].firstChild; if (firstChild) { _queryNodeChildren(firstChild, childView, predicate, matches, elementsOnly, rootNativeNode); } } } /** * Match the current native node against the predicate. * * @param nativeNode the current native node * @param predicate the predicate to match * @param matches the list of positive matches * @param elementsOnly whether only elements should be searched * @param rootNativeNode the root native node on which predicate should not be matched */ function _addQueryMatch(nativeNode, predicate, matches, elementsOnly, rootNativeNode) { if (rootNativeNode !== nativeNode) { const debugNode = getDebugNode(nativeNode); if (!debugNode) { return; } // Type of the "predicate and "matches" array are set based on the value of // the "elementsOnly" parameter. TypeScript is not able to properly infer these // types with generics, so we manually cast the parameters accordingly. if (elementsOnly && (debugNode instanceof DebugElement) && predicate(debugNode) && matches.indexOf(debugNode) === -1) { matches.push(debugNode); } else if (!elementsOnly && predicate(debugNode) && matches.indexOf(debugNode) === -1) { matches.push(debugNode); } } } /** * Match all the descendants of a DOM node against a predicate. * * @param nativeNode the current native node * @param predicate the predicate to match * @param matches the list where matches are stored * @param elementsOnly whether only elements should be searched */ function _queryNativeNodeDescendants(parentNode, predicate, matches, elementsOnly) { const nodes = parentNode.childNodes; const length = nodes.length; for (let i = 0; i < length; i++) { const node = nodes[i]; const debugNode = getDebugNode(node); if (debugNode) { if (elementsOnly && (debugNode instanceof DebugElement) && predicate(debugNode) && matches.indexOf(debugNode) === -1) { matches.push(debugNode); } else if (!elementsOnly && predicate(debugNode) && matches.indexOf(debugNode) === -1) { matches.push(debugNode); } _queryNativeNodeDescendants(node, predicate, matches, elementsOnly); } } } /** * Iterates through the property bindings for a given node and generates * a map of property names to values. This map only contains property bindings * defined in templates, not in host bindings. */ function collectPropertyBindings(properties, tNode, lView, tData) { let bindingIndexes = tNode.propertyBindings; if (bindingIndexes !== null) { for (let i = 0; i < bindingIndexes.length; i++) { const bindingIndex = bindingIndexes[i]; const propMetadata = tData[bindingIndex]; const metadataParts = propMetadata.split(INTERPOLATION_DELIMITER); const propertyName = metadataParts[0]; if (metadataParts.length > 1) { let value = metadataParts[1]; for (let j = 1; j < metadataParts.length - 1; j++) { value += renderStringify(lView[bindingIndex + j - 1]) + metadataParts[j + 1]; } properties[propertyName] = value; } else { properties[propertyName] = lView[bindingIndex]; } } } } const NG_DEBUG_PROPERTY = '__ng_debug__'; /** * @publicApi */ function getDebugNode(nativeNode) { if (nativeNode instanceof Node) { if (!(nativeNode.hasOwnProperty(NG_DEBUG_PROPERTY))) { nativeNode[NG_DEBUG_PROPERTY] = nativeNode.nodeType == Node.ELEMENT_NODE ? new DebugElement(nativeNode) : new DebugNode(nativeNode); } return nativeNode[NG_DEBUG_PROPERTY]; } return null; } class DefaultIterableDifferFactory { constructor() { } supports(obj) { return isListLikeIterable(obj); } create(trackByFn) { return new DefaultIterableDiffer(trackByFn); } } const trackByIdentity = (index, item) => item; /** * @deprecated v4.0.0 - Should not be part of public API. * @publicApi */ class DefaultIterableDiffer { constructor(trackByFn) { this.length = 0; // Keeps track of the used records at any point in time (during & across `_check()` calls) this._linkedRecords = null; // Keeps track of the removed records at any point in time during `_check()` calls. this._unlinkedRecords = null; this._previousItHead = null; this._itHead = null; this._itTail = null; this._additionsHead = null; this._additionsTail = null; this._movesHead = null; this._movesTail = null; this._removalsHead = null; this._removalsTail = null; // Keeps track of records where custom track by is the same, but item identity has changed this._identityChangesHead = null; this._identityChangesTail = null; this._trackByFn = trackByFn || trackByIdentity; } forEachItem(fn) { let record; for (record = this._itHead; record !== null; record = record._next) { fn(record); } } forEachOperation(fn) { let nextIt = this._itHead; let nextRemove = this._removalsHead; let addRemoveOffset = 0; let moveOffsets = null; while (nextIt || nextRemove) { // Figure out which is the next record to process // Order: remove, add, move const record = !nextRemove || nextIt && nextIt.currentIndex < getPreviousIndex(nextRemove, addRemoveOffset, moveOffsets) ? nextIt : nextRemove; const adjPreviousIndex = getPreviousIndex(record, addRemoveOffset, moveOffsets); const currentIndex = record.currentIndex; // consume the item, and adjust the addRemoveOffset and update moveDistance if necessary if (record === nextRemove) { addRemoveOffset--; nextRemove = nextRemove._nextRemoved; } else { nextIt = nextIt._next; if (record.previousIndex == null) { addRemoveOffset++; } else { // INVARIANT: currentIndex < previousIndex if (!moveOffsets) moveOffsets = []; const localMovePreviousIndex = adjPreviousIndex - addRemoveOffset; const localCurrentIndex = currentIndex - addRemoveOffset; if (localMovePreviousIndex != localCurrentIndex) { for (let i = 0; i < localMovePreviousIndex; i++) { const offset = i < moveOffsets.length ? moveOffsets[i] : (moveOffsets[i] = 0); const index = offset + i; if (localCurrentIndex <= index && index < localMovePreviousIndex) { moveOffsets[i] = offset + 1; } } const previousIndex = record.previousIndex; moveOffsets[previousIndex] = localCurrentIndex - localMovePreviousIndex; } } } if (adjPreviousIndex !== currentIndex) { fn(record, adjPreviousIndex, currentIndex); } } } forEachPreviousItem(fn) { let record; for (record = this._previousItHead; record !== null; record = record._nextPrevious) { fn(record); } } forEachAddedItem(fn) { let record; for (record = this._additionsHead; record !== null; record = record._nextAdded) { fn(record); } } forEachMovedItem(fn) { let record; for (record = this._movesHead; record !== null; record = record._nextMoved) { fn(record); } } forEachRemovedItem(fn) { let record; for (record = this._removalsHead; record !== null; record = record._nextRemoved) { fn(record); } } forEachIdentityChange(fn) { let record; for (record = this._identityChangesHead; record !== null; record = record._nextIdentityChange) { fn(record); } } diff(collection) { if (collection == null) collection = []; if (!isListLikeIterable(collection)) { throw new RuntimeError(900 /* RuntimeErrorCode.INVALID_DIFFER_INPUT */, ngDevMode && `Error trying to diff '${stringify(collection)}'. Only arrays and iterables are allowed`); } if (this.check(collection)) { return this; } else { return null; } } onDestroy() { } check(collection) { this._reset(); let record = this._itHead; let mayBeDirty = false; let index; let item; let itemTrackBy; if (Array.isArray(collection)) { this.length = collection.length; for (let index = 0; index < this.length; index++) { item = collection[index]; itemTrackBy = this._trackByFn(index, item); if (record === null || !Object.is(record.trackById, itemTrackBy)) { record = this._mismatch(record, item, itemTrackBy, index); mayBeDirty = true; } else { if (mayBeDirty) { // TODO(misko): can we limit this to duplicates only? record = this._verifyReinsertion(record, item, itemTrackBy, index); } if (!Object.is(record.item, item)) this._addIdentityChange(record, item); } record = record._next; } } else { index = 0; iterateListLike(collection, (item) => { itemTrackBy = this._trackByFn(index, item); if (record === null || !Object.is(record.trackById, itemTrackBy)) { record = this._mismatch(record, item, itemTrackBy, index); mayBeDirty = true; } else { if (mayBeDirty) { // TODO(misko): can we limit this to duplicates only? record = this._verifyReinsertion(record, item, itemTrackBy, index); } if (!Object.is(record.item, item)) this._addIdentityChange(record, item); } record = record._next; index++; }); this.length = index; } this._truncate(record); this.collection = collection; return this.isDirty; } /* CollectionChanges is considered dirty if it has any additions, moves, removals, or identity * changes. */ get isDirty() { return this._additionsHead !== null || this._movesHead !== null || this._removalsHead !== null || this._identityChangesHead !== null; } /** * Reset the state of the change objects to show no changes. This means set previousKey to * currentKey, and clear all of the queues (additions, moves, removals). * Set the previousIndexes of moved and added items to their currentIndexes * Reset the list of additions, moves and removals * * @internal */ _reset() { if (this.isDirty) { let record; for (record = this._previousItHead = this._itHead; record !== null; record = record._next) { record._nextPrevious = record._next; } for (record = this._additionsHead; record !== null; record = record._nextAdded) { record.previousIndex = record.currentIndex; } this._additionsHead = this._additionsTail = null; for (record = this._movesHead; record !== null; record = record._nextMoved) { record.previousIndex = record.currentIndex; } this._movesHead = this._movesTail = null; this._removalsHead = this._removalsTail = null; this._identityChangesHead = this._identityChangesTail = null; // TODO(vicb): when assert gets supported // assert(!this.isDirty); } } /** * This is the core function which handles differences between collections. * * - `record` is the record which we saw at this position last time. If null then it is a new * item. * - `item` is the current item in the collection * - `index` is the position of the item in the collection * * @internal */ _mismatch(record, item, itemTrackBy, index) { // The previous record after which we will append the current one. let previousRecord; if (record === null) { previousRecord = this._itTail; } else { previousRecord = record._prev; // Remove the record from the collection since we know it does not match the item. this._remove(record); } // See if we have evicted the item, which used to be at some anterior position of _itHead list. record = this._unlinkedRecords === null ? null : this._unlinkedRecords.get(itemTrackBy, null); if (record !== null) { // It is an item which we have evicted earlier: reinsert it back into the list. // But first we need to check if identity changed, so we can update in view if necessary. if (!Object.is(record.item, item)) this._addIdentityChange(record, item); this._reinsertAfter(record, previousRecord, index); } else { // Attempt to see if the item is at some posterior position of _itHead list. record = this._linkedRecords === null ? null : this._linkedRecords.get(itemTrackBy, index); if (record !== null) { // We have the item in _itHead at/after `index` position. We need to move it forward in the // collection. // But first we need to check if identity changed, so we can update in view if necessary. if (!Object.is(record.item, item)) this._addIdentityChange(record, item); this._moveAfter(record, previousRecord, index); } else { // It is a new item: add it. record = this._addAfter(new IterableChangeRecord_(item, itemTrackBy), previousRecord, index); } } return record; } /** * This check is only needed if an array contains duplicates. (Short circuit of nothing dirty) * * Use case: `[a, a]` => `[b, a, a]` * * If we did not have this check then the insertion of `b` would: * 1) evict first `a` * 2) insert `b` at `0` index. * 3) leave `a` at index `1` as is. <-- this is wrong! * 3) reinsert `a` at index 2. <-- this is wrong! * * The correct behavior is: * 1) evict first `a` * 2) insert `b` at `0` index. * 3) reinsert `a` at index 1. * 3) move `a` at from `1` to `2`. * * * Double check that we have not evicted a duplicate item. We need to check if the item type may * have already been removed: * The insertion of b will evict the first 'a'. If we don't reinsert it now it will be reinserted * at the end. Which will show up as the two 'a's switching position. This is incorrect, since a * better way to think of it is as insert of 'b' rather then switch 'a' with 'b' and then add 'a' * at the end. * * @internal */ _verifyReinsertion(record, item, itemTrackBy, index) { let reinsertRecord = this._unlinkedRecords === null ? null : this._unlinkedRecords.get(itemTrackBy, null); if (reinsertRecord !== null) { record = this._reinsertAfter(reinsertRecord, record._prev, index); } else if (record.currentIndex != index) { record.currentIndex = index; this._addToMoves(record, index); } return record; } /** * Get rid of any excess {@link IterableChangeRecord_}s from the previous collection * * - `record` The first excess {@link IterableChangeRecord_}. * * @internal */ _truncate(record) { // Anything after that needs to be removed; while (record !== null) { const nextRecord = record._next; this._addToRemovals(this._unlink(record)); record = nextRecord; } if (this._unlinkedRecords !== null) { this._unlinkedRecords.clear(); } if (this._additionsTail !== null) { this._additionsTail._nextAdded = null; } if (this._movesTail !== null) { this._movesTail._nextMoved = null; } if (this._itTail !== null) { this._itTail._next = null; } if (this._removalsTail !== null) { this._removalsTail._nextRemoved = null; } if (this._identityChangesTail !== null) { this._identityChangesTail._nextIdentityChange = null; } } /** @internal */ _reinsertAfter(record, prevRecord, index) { if (this._unlinkedRecords !== null) { this._unlinkedRecords.remove(record); } const prev = record._prevRemoved; const next = record._nextRemoved; if (prev === null) { this._removalsHead = next; } else { prev._nextRemoved = next; } if (next === null) { this._removalsTail = prev; } else { next._prevRemoved = prev; } this._insertAfter(record, prevRecord, index); this._addToMoves(record, index); return record; } /** @internal */ _moveAfter(record, prevRecord, index) { this._unlink(record); this._insertAfter(record, prevRecord, index); this._addToMoves(record, index); return record; } /** @internal */ _addAfter(record, prevRecord, index) { this._insertAfter(record, prevRecord, index); if (this._additionsTail === null) { // TODO(vicb): // assert(this._additionsHead === null); this._additionsTail = this._additionsHead = record; } else { // TODO(vicb): // assert(_additionsTail._nextAdded === null); // assert(record._nextAdded === null); this._additionsTail = this._additionsTail._nextAdded = record; } return record; } /** @internal */ _insertAfter(record, prevRecord, index) { // TODO(vicb): // assert(record != prevRecord); // assert(record._next === null); // assert(record._prev === null); const next = prevRecord === null ? this._itHead : prevRecord._next; // TODO(vicb): // assert(next != record); // assert(prevRecord != record); record._next = next; record._prev = prevRecord; if (next === null) { this._itTail = record; } else { next._prev = record; } if (prevRecord === null) { this._itHead = record; } else { prevRecord._next = record; } if (this._linkedRecords === null) { this._linkedRecords = new _DuplicateMap(); } this._linkedRecords.put(record); record.currentIndex = index; return record; } /** @internal */ _remove(record) { return this._addToRemovals(this._unlink(record)); } /** @internal */ _unlink(record) { if (this._linkedRecords !== null) { this._linkedRecords.remove(record); } const prev = record._prev; const next = record._next; // TODO(vicb): // assert((record._prev = null) === null); // assert((record._next = null) === null); if (prev === null) { this._itHead = next; } else { prev._next = next; } if (next === null) { this._itTail = prev; } else { next._prev = prev; } return record; } /** @internal */ _addToMoves(record, toIndex) { // TODO(vicb): // assert(record._nextMoved === null); if (record.previousIndex === toIndex) { return record; } if (this._movesTail === null) { // TODO(vicb): // assert(_movesHead === null); this._movesTail = this._movesHead = record; } else { // TODO(vicb): // assert(_movesTail._nextMoved === null); this._movesTail = this._movesTail._nextMoved = record; } return record; } _addToRemovals(record) { if (this._unlinkedRecords === null) { this._unlinkedRecords = new _DuplicateMap(); } this._unlinkedRecords.put(record); record.currentIndex = null; record._nextRemoved = null; if (this._removalsTail === null) { // TODO(vicb): // assert(_removalsHead === null); this._removalsTail = this._removalsHead = record; record._prevRemoved = null; } else { // TODO(vicb): // assert(_removalsTail._nextRemoved === null); // assert(record._nextRemoved === null); record._prevRemoved = this._removalsTail; this._removalsTail = this._removalsTail._nextRemoved = record; } return record; } /** @internal */ _addIdentityChange(record, item) { record.item = item; if (this._identityChangesTail === null) { this._identityChangesTail = this._identityChangesHead = record; } else { this._identityChangesTail = this._identityChangesTail._nextIdentityChange = record; } return record; } } exports('DefaultIterableDiffer', DefaultIterableDiffer); class IterableChangeRecord_ { constructor(item, trackById) { this.item = item; this.trackById = trackById; this.currentIndex = null; this.previousIndex = null; /** @internal */ this._nextPrevious = null; /** @internal */ this._prev = null; /** @internal */ this._next = null; /** @internal */ this._prevDup = null; /** @internal */ this._nextDup = null; /** @internal */ this._prevRemoved = null; /** @internal */ this._nextRemoved = null; /** @internal */ this._nextAdded = null; /** @internal */ this._nextMoved = null; /** @internal */ this._nextIdentityChange = null; } } // A linked list of IterableChangeRecords with the same IterableChangeRecord_.item class _DuplicateItemRecordList { constructor() { /** @internal */ this._head = null; /** @internal */ this._tail = null; } /** * Append the record to the list of duplicates. * * Note: by design all records in the list of duplicates hold the same value in record.item. */ add(record) { if (this._head === null) { this._head = this._tail = record; record._nextDup = null; record._prevDup = null; } else { // TODO(vicb): // assert(record.item == _head.item || // record.item is num && record.item.isNaN && _head.item is num && _head.item.isNaN); this._tail._nextDup = record; record._prevDup = this._tail; record._nextDup = null; this._tail = record; } } // Returns a IterableChangeRecord_ having IterableChangeRecord_.trackById == trackById and // IterableChangeRecord_.currentIndex >= atOrAfterIndex get(trackById, atOrAfterIndex) { let record; for (record = this._head; record !== null; record = record._nextDup) { if ((atOrAfterIndex === null || atOrAfterIndex <= record.currentIndex) && Object.is(record.trackById, trackById)) { return record; } } return null; } /** * Remove one {@link IterableChangeRecord_} from the list of duplicates. * * Returns whether the list of duplicates is empty. */ remove(record) { // TODO(vicb): // assert(() { // // verify that the record being removed is in the list. // for (IterableChangeRecord_ cursor = _head; cursor != null; cursor = cursor._nextDup) { // if (identical(cursor, record)) return true; // } // return false; //}); const prev = record._prevDup; const next = record._nextDup; if (prev === null) { this._head = next; } else { prev._nextDup = next; } if (next === null) { this._tail = prev; } else { next._prevDup = prev; } return this._head === null; } } class _DuplicateMap { constructor() { this.map = new Map(); } put(record) { const key = record.trackById; let duplicates = this.map.get(key); if (!duplicates) { duplicates = new _DuplicateItemRecordList(); this.map.set(key, duplicates); } duplicates.add(record); } /** * Retrieve the `value` using key. Because the IterableChangeRecord_ value may be one which we * have already iterated over, we use the `atOrAfterIndex` to pretend it is not there. * * Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we * have any more `a`s needs to return the second `a`. */ get(trackById, atOrAfterIndex) { const key = trackById; const recordList = this.map.get(key); return recordList ? recordList.get(trackById, atOrAfterIndex) : null; } /** * Removes a {@link IterableChangeRecord_} from the list of duplicates. * * The list of duplicates also is removed from the map if it gets empty. */ remove(record) { const key = record.trackById; const recordList = this.map.get(key); // Remove the list of duplicates when it gets empty if (recordList.remove(record)) { this.map.delete(key); } return record; } get isEmpty() { return this.map.size === 0; } clear() { this.map.clear(); } } function getPreviousIndex(item, addRemoveOffset, moveOffsets) { const previousIndex = item.previousIndex; if (previousIndex === null) return previousIndex; let moveOffset = 0; if (moveOffsets && previousIndex < moveOffsets.length) { moveOffset = moveOffsets[previousIndex]; } return previousIndex + addRemoveOffset + moveOffset; } class DefaultKeyValueDifferFactory { constructor() { } supports(obj) { return obj instanceof Map || isJsObject(obj); } create() { return new DefaultKeyValueDiffer(); } } class DefaultKeyValueDiffer { constructor() { this._records = new Map(); this._mapHead = null; // _appendAfter is used in the check loop this._appendAfter = null; this._previousMapHead = null; this._changesHead = null; this._changesTail = null; this._additionsHead = null; this._additionsTail = null; this._removalsHead = null; this._removalsTail = null; } get isDirty() { return this._additionsHead !== null || this._changesHead !== null || this._removalsHead !== null; } forEachItem(fn) { let record; for (record = this._mapHead; record !== null; record = record._next) { fn(record); } } forEachPreviousItem(fn) { let record; for (record = this._previousMapHead; record !== null; record = record._nextPrevious) { fn(record); } } forEachChangedItem(fn) { let record; for (record = this._changesHead; record !== null; record = record._nextChanged) { fn(record); } } forEachAddedItem(fn) { let record; for (record = this._additionsHead; record !== null; record = record._nextAdded) { fn(record); } } forEachRemovedItem(fn) { let record; for (record = this._removalsHead; record !== null; record = record._nextRemoved) { fn(record); } } diff(map) { if (!map) { map = new Map(); } else if (!(map instanceof Map || isJsObject(map))) { throw new RuntimeError(900 /* RuntimeErrorCode.INVALID_DIFFER_INPUT */, ngDevMode && `Error trying to diff '${stringify(map)}'. Only maps and objects are allowed`); } return this.check(map) ? this : null; } onDestroy() { } /** * Check the current state of the map vs the previous. * The algorithm is optimised for when the keys do no change. */ check(map) { this._reset(); let insertBefore = this._mapHead; this._appendAfter = null; this._forEach(map, (value, key) => { if (insertBefore && insertBefore.key === key) { this._maybeAddToChanges(insertBefore, value); this._appendAfter = insertBefore; insertBefore = insertBefore._next; } else { const record = this._getOrCreateRecordForKey(key, value); insertBefore = this._insertBeforeOrAppend(insertBefore, record); } }); // Items remaining at the end of the list have been deleted if (insertBefore) { if (insertBefore._prev) { insertBefore._prev._next = null; } this._removalsHead = insertBefore; for (let record = insertBefore; record !== null; record = record._nextRemoved) { if (record === this._mapHead) { this._mapHead = null; } this._records.delete(record.key); record._nextRemoved = record._next; record.previousValue = record.currentValue; record.currentValue = null; record._prev = null; record._next = null; } } // Make sure tails have no next records from previous runs if (this._changesTail) this._changesTail._nextChanged = null; if (this._additionsTail) this._additionsTail._nextAdded = null; return this.isDirty; } /** * Inserts a record before `before` or append at the end of the list when `before` is null. * * Notes: * - This method appends at `this._appendAfter`, * - This method updates `this._appendAfter`, * - The return value is the new value for the insertion pointer. */ _insertBeforeOrAppend(before, record) { if (before) { const prev = before._prev; record._next = before; record._prev = prev; before._prev = record; if (prev) { prev._next = record; } if (before === this._mapHead) { this._mapHead = record; } this._appendAfter = before; return before; } if (this._appendAfter) { this._appendAfter._next = record; record._prev = this._appendAfter; } else { this._mapHead = record; } this._appendAfter = record; return null; } _getOrCreateRecordForKey(key, value) { if (this._records.has(key)) { const record = this._records.get(key); this._maybeAddToChanges(record, value); const prev = record._prev; const next = record._next; if (prev) { prev._next = next; } if (next) { next._prev = prev; } record._next = null; record._prev = null; return record; } const record = new KeyValueChangeRecord_(key); this._records.set(key, record); record.currentValue = value; this._addToAdditions(record); return record; } /** @internal */ _reset() { if (this.isDirty) { let record; // let `_previousMapHead` contain the state of the map before the changes this._previousMapHead = this._mapHead; for (record = this._previousMapHead; record !== null; record = record._next) { record._nextPrevious = record._next; } // Update `record.previousValue` with the value of the item before the changes // We need to update all changed items (that's those which have been added and changed) for (record = this._changesHead; record !== null; record = record._nextChanged) { record.previousValue = record.currentValue; } for (record = this._additionsHead; record != null; record = record._nextAdded) { record.previousValue = record.currentValue; } this._changesHead = this._changesTail = null; this._additionsHead = this._additionsTail = null; this._removalsHead = null; } } // Add the record or a given key to the list of changes only when the value has actually changed _maybeAddToChanges(record, newValue) { if (!Object.is(newValue, record.currentValue)) { record.previousValue = record.currentValue; record.currentValue = newValue; this._addToChanges(record); } } _addToAdditions(record) { if (this._additionsHead === null) { this._additionsHead = this._additionsTail = record; } else { this._additionsTail._nextAdded = record; this._additionsTail = record; } } _addToChanges(record) { if (this._changesHead === null) { this._changesHead = this._changesTail = record; } else { this._changesTail._nextChanged = record; this._changesTail = record; } } /** @internal */ _forEach(obj, fn) { if (obj instanceof Map) { obj.forEach(fn); } else { Object.keys(obj).forEach(k => fn(obj[k], k)); } } } class KeyValueChangeRecord_ { constructor(key) { this.key = key; this.previousValue = null; this.currentValue = null; /** @internal */ this._nextPrevious = null; /** @internal */ this._next = null; /** @internal */ this._prev = null; /** @internal */ this._nextAdded = null; /** @internal */ this._nextRemoved = null; /** @internal */ this._nextChanged = null; } } function defaultIterableDiffersFactory() { return new IterableDiffers([new DefaultIterableDifferFactory()]); } /** * A repository of different iterable diffing strategies used by NgFor, NgClass, and others. * * @publicApi */ class IterableDiffers { /** @nocollapse */ static { this.ɵprov = ɵɵdefineInjectable({ token: IterableDiffers, providedIn: 'root', factory: defaultIterableDiffersFactory }); } constructor(factories) { this.factories = factories; } static create(factories, parent) { if (parent != null) { const copied = parent.factories.slice(); factories = factories.concat(copied); } return new IterableDiffers(factories); } /** * Takes an array of {@link IterableDifferFactory} and returns a provider used to extend the * inherited {@link IterableDiffers} instance with the provided factories and return a new * {@link IterableDiffers} instance. * * @usageNotes * ### Example * * The following example shows how to extend an existing list of factories, * which will only be applied to the injector for this component and its children. * This step is all that's required to make a new {@link IterableDiffer} available. * * ``` * @Component({ * viewProviders: [ * IterableDiffers.extend([new ImmutableListDiffer()]) * ] * }) * ``` */ static extend(factories) { return { provide: IterableDiffers, useFactory: (parent) => { // if parent is null, it means that we are in the root injector and we have just overridden // the default injection mechanism for IterableDiffers, in such a case just assume // `defaultIterableDiffersFactory`. return IterableDiffers.create(factories, parent || defaultIterableDiffersFactory()); }, // Dependency technically isn't optional, but we can provide a better error message this way. deps: [[IterableDiffers, new SkipSelf(), new Optional()]] }; } find(iterable) { const factory = this.factories.find(f => f.supports(iterable)); if (factory != null) { return factory; } else { throw new RuntimeError(901 /* RuntimeErrorCode.NO_SUPPORTING_DIFFER_FACTORY */, ngDevMode && `Cannot find a differ supporting object '${iterable}' of type '${getTypeNameForDebugging(iterable)}'`); } } } exports('IterableDiffers', IterableDiffers); function getTypeNameForDebugging(type) { return type['name'] || typeof type; } function defaultKeyValueDiffersFactory() { return new KeyValueDiffers([new DefaultKeyValueDifferFactory()]); } /** * A repository of different Map diffing strategies used by NgClass, NgStyle, and others. * * @publicApi */ class KeyValueDiffers { /** @nocollapse */ static { this.ɵprov = ɵɵdefineInjectable({ token: KeyValueDiffers, providedIn: 'root', factory: defaultKeyValueDiffersFactory }); } constructor(factories) { this.factories = factories; } static create(factories, parent) { if (parent) { const copied = parent.factories.slice(); factories = factories.concat(copied); } return new KeyValueDiffers(factories); } /** * Takes an array of {@link KeyValueDifferFactory} and returns a provider used to extend the * inherited {@link KeyValueDiffers} instance with the provided factories and return a new * {@link KeyValueDiffers} instance. * * @usageNotes * ### Example * * The following example shows how to extend an existing list of factories, * which will only be applied to the injector for this component and its children. * This step is all that's required to make a new {@link KeyValueDiffer} available. * * ``` * @Component({ * viewProviders: [ * KeyValueDiffers.extend([new ImmutableMapDiffer()]) * ] * }) * ``` */ static extend(factories) { return { provide: KeyValueDiffers, useFactory: (parent) => { // if parent is null, it means that we are in the root injector and we have just overridden // the default injection mechanism for KeyValueDiffers, in such a case just assume // `defaultKeyValueDiffersFactory`. return KeyValueDiffers.create(factories, parent || defaultKeyValueDiffersFactory()); }, // Dependency technically isn't optional, but we can provide a better error message this way. deps: [[KeyValueDiffers, new SkipSelf(), new Optional()]] }; } find(kv) { const factory = this.factories.find(f => f.supports(kv)); if (factory) { return factory; } throw new RuntimeError(901 /* RuntimeErrorCode.NO_SUPPORTING_DIFFER_FACTORY */, ngDevMode && `Cannot find a differ supporting object '${kv}'`); } } exports('KeyValueDiffers', KeyValueDiffers); /** * Structural diffing for `Object`s and `Map`s. */ const keyValDiff = [new DefaultKeyValueDifferFactory()]; /** * Structural diffing for `Iterable` types such as `Array`s. */ const iterableDiff = [new DefaultIterableDifferFactory()]; const defaultIterableDiffers = exports('ɵdefaultIterableDiffers', new IterableDiffers(iterableDiff)); const defaultKeyValueDiffers = exports('ɵdefaultKeyValueDiffers', new KeyValueDiffers(keyValDiff)); /** * @module * @description * Change detection enables data binding in Angular. */ /** * This platform has to be included in any other platform * * @publicApi */ const platformCore = exports('platformCore', createPlatformFactory(null, 'core', [])); /** * Re-exported by `BrowserModule`, which is included automatically in the root * `AppModule` when you create a new app with the CLI `new` command. Eagerly injects * `ApplicationRef` to instantiate it. * * @publicApi */ class ApplicationModule { // Inject ApplicationRef to make it eager... constructor(appRef) { } static { this.ɵfac = function ApplicationModule_Factory(t) { return new (t || ApplicationModule)(ɵɵinject(ApplicationRef)); }; } static { this.ɵmod = /*@__PURE__*/ ɵɵdefineNgModule({ type: ApplicationModule }); } static { this.ɵinj = /*@__PURE__*/ ɵɵdefineInjector({}); } } exports('ApplicationModule', ApplicationModule); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ApplicationModule, [{ type: NgModule }], function () { return [{ type: ApplicationRef }]; }, null); })(); /** * A collection that tracks all serialized views (`ngh` DOM annotations) * to avoid duplication. An attempt to add a duplicate view results in the * collection returning the index of the previously collected serialized view. * This reduces the number of annotations needed for a given page. */ class SerializedViewCollection { constructor() { this.views = []; this.indexByContent = new Map(); } add(serializedView) { const viewAsString = JSON.stringify(serializedView); if (!this.indexByContent.has(viewAsString)) { const index = this.views.length; this.views.push(serializedView); this.indexByContent.set(viewAsString, index); return index; } return this.indexByContent.get(viewAsString); } getAll() { return this.views; } } /** * Global counter that is used to generate a unique id for TViews * during the serialization process. */ let tViewSsrId = 0; /** * Generates a unique id for a given TView and returns this id. * The id is also stored on this instance of a TView and reused in * subsequent calls. * * This id is needed to uniquely identify and pick up dehydrated views * at runtime. */ function getSsrId(tView) { if (!tView.ssrId) { tView.ssrId = `t${tViewSsrId++}`; } return tView.ssrId; } /** * Computes the number of root nodes in a given view * (or child nodes in a given container if a tNode is provided). */ function calcNumRootNodes(tView, lView, tNode) { const rootNodes = []; collectNativeNodes(tView, lView, tNode, rootNodes); return rootNodes.length; } /** * Annotates all components bootstrapped in a given ApplicationRef * with info needed for hydration. * * @param appRef An instance of an ApplicationRef. * @param doc A reference to the current Document instance. */ function annotateForHydration(appRef, doc) { const serializedViewCollection = new SerializedViewCollection(); const corruptedTextNodes = new Map(); const viewRefs = appRef._views; for (const viewRef of viewRefs) { const lView = getComponentLViewForHydration(viewRef); // An `lView` might be `null` if a `ViewRef` represents // an embedded view (not a component view). if (lView !== null) { const hostElement = lView[HOST]; // Root elements might also be annotated with the `ngSkipHydration` attribute, // check if it's present before starting the serialization process. if (hostElement && !hostElement.hasAttribute(SKIP_HYDRATION_ATTR_NAME)) { const context = { serializedViewCollection, corruptedTextNodes, }; annotateHostElementForHydration(hostElement, lView, context); insertCorruptedTextNodeMarkers(corruptedTextNodes, doc); } } } // Note: we *always* include hydration info key and a corresponding value // into the TransferState, even if the list of serialized views is empty. // This is needed as a signal to the client that the server part of the // hydration logic was setup and enabled correctly. Otherwise, if a client // hydration doesn't find a key in the transfer state - an error is produced. const serializedViews = serializedViewCollection.getAll(); const transferState = appRef.injector.get(TransferState); transferState.set(NGH_DATA_KEY, serializedViews); } /** * Serializes the lContainer data into a list of SerializedView objects, * that represent views within this lContainer. * * @param lContainer the lContainer we are serializing * @param context the hydration context * @returns an array of the `SerializedView` objects */ function serializeLContainer(lContainer, context) { const views = []; let lastViewAsString = ''; for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) { let childLView = lContainer[i]; // If this is a root view, get an LView for the underlying component, // because it contains information about the view to serialize. if (isRootView(childLView)) { childLView = childLView[HEADER_OFFSET]; } const childTView = childLView[TVIEW]; let template; let numRootNodes = 0; if (childTView.type === 1 /* TViewType.Component */) { template = childTView.ssrId; // This is a component view, thus it has only 1 root node: the component // host node itself (other nodes would be inside that host node). numRootNodes = 1; } else { template = getSsrId(childTView); numRootNodes = calcNumRootNodes(childTView, childLView, childTView.firstChild); } const view = { [TEMPLATE_ID]: template, [NUM_ROOT_NODES]: numRootNodes, ...serializeLView(lContainer[i], context), }; // Check if the previous view has the same shape (for example, it was // produced by the *ngFor), in which case bump the counter on the previous // view instead of including the same information again. const currentViewAsString = JSON.stringify(view); if (views.length > 0 && currentViewAsString === lastViewAsString) { const previousView = views[views.length - 1]; previousView[MULTIPLIER] ??= 1; previousView[MULTIPLIER]++; } else { // Record this view as most recently added. lastViewAsString = currentViewAsString; views.push(view); } } return views; } /** * Helper function to produce a node path (which navigation steps runtime logic * needs to take to locate a node) and stores it in the `NODES` section of the * current serialized view. */ function appendSerializedNodePath(ngh, tNode, lView) { const noOffsetIndex = tNode.index - HEADER_OFFSET; ngh[NODES] ??= {}; ngh[NODES][noOffsetIndex] = calcPathForNode(tNode, lView); } /** * Helper function to append information about a disconnected node. * This info is needed at runtime to avoid DOM lookups for this element * and instead, the element would be created from scratch. */ function appendDisconnectedNodeIndex(ngh, tNode) { const noOffsetIndex = tNode.index - HEADER_OFFSET; ngh[DISCONNECTED_NODES] ??= []; if (!ngh[DISCONNECTED_NODES].includes(noOffsetIndex)) { ngh[DISCONNECTED_NODES].push(noOffsetIndex); } } /** * Serializes the lView data into a SerializedView object that will later be added * to the TransferState storage and referenced using the `ngh` attribute on a host * element. * * @param lView the lView we are serializing * @param context the hydration context * @returns the `SerializedView` object containing the data to be added to the host node */ function serializeLView(lView, context) { const ngh = {}; const tView = lView[TVIEW]; // Iterate over DOM element references in an LView. for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) { const tNode = tView.data[i]; const noOffsetIndex = i - HEADER_OFFSET; // Local refs (e.g.
    ) take up an extra slot in LViews // to store the same element. In this case, there is no information in // a corresponding slot in TNode data structure. If that's the case, just // skip this slot and move to the next one. if (!tNode) { continue; } // Check if a native node that represents a given TNode is disconnected from the DOM tree. // Such nodes must be excluded from the hydration (since the hydration won't be able to // find them), so the TNode ids are collected and used at runtime to skip the hydration. // // This situation may happen during the content projection, when some nodes don't make it // into one of the content projection slots (for example, when there is no default // slot in projector component's template). if (isDisconnectedNode(tNode, lView) && isContentProjectedNode(tNode)) { appendDisconnectedNodeIndex(ngh, tNode); continue; } if (Array.isArray(tNode.projection)) { for (const projectionHeadTNode of tNode.projection) { // We may have `null`s in slots with no projected content. if (!projectionHeadTNode) continue; if (!Array.isArray(projectionHeadTNode)) { // If we process re-projected content (i.e. `` // appears at projection location), skip annotations for this content // since all DOM nodes in this projection were handled while processing // a parent lView, which contains those nodes. if (!isProjectionTNode(projectionHeadTNode) && !isInSkipHydrationBlock(projectionHeadTNode)) { if (isDisconnectedNode(projectionHeadTNode, lView)) { // Check whether this node is connected, since we may have a TNode // in the data structure as a projection segment head, but the // content projection slot might be disabled (e.g. // ). appendDisconnectedNodeIndex(ngh, projectionHeadTNode); } else { appendSerializedNodePath(ngh, projectionHeadTNode, lView); } } } else { // If a value is an array, it means that we are processing a projection // where projectable nodes were passed in as DOM nodes (for example, when // calling `ViewContainerRef.createComponent(CmpA, {projectableNodes: [...]})`). // // In this scenario, nodes can come from anywhere (either created manually, // accessed via `document.querySelector`, etc) and may be in any state // (attached or detached from the DOM tree). As a result, we can not reliably // restore the state for such cases during hydration. throw unsupportedProjectionOfDomNodes(unwrapRNode(lView[i])); } } } if (isLContainer(lView[i])) { // Serialize information about a template. const embeddedTView = tNode.tView; if (embeddedTView !== null) { ngh[TEMPLATES] ??= {}; ngh[TEMPLATES][noOffsetIndex] = getSsrId(embeddedTView); } // Serialize views within this LContainer. const hostNode = lView[i][HOST]; // host node of this container // LView[i][HOST] can be of 2 different types: // - either a DOM node // - or an array that represents an LView of a component if (Array.isArray(hostNode)) { // This is a component, serialize info about it. const targetNode = unwrapRNode(hostNode); if (!targetNode.hasAttribute(SKIP_HYDRATION_ATTR_NAME)) { annotateHostElementForHydration(targetNode, hostNode, context); } } ngh[CONTAINERS] ??= {}; ngh[CONTAINERS][noOffsetIndex] = serializeLContainer(lView[i], context); } else if (Array.isArray(lView[i])) { // This is a component, annotate the host node with an `ngh` attribute. const targetNode = unwrapRNode(lView[i][HOST]); if (!targetNode.hasAttribute(SKIP_HYDRATION_ATTR_NAME)) { annotateHostElementForHydration(targetNode, lView[i], context); } } else { // case if (tNode.type & 8 /* TNodeType.ElementContainer */) { // An is represented by the number of // top-level nodes. This information is needed to skip over // those nodes to reach a corresponding anchor node (comment node). ngh[ELEMENT_CONTAINERS] ??= {}; ngh[ELEMENT_CONTAINERS][noOffsetIndex] = calcNumRootNodes(tView, lView, tNode.child); } else if (tNode.type & 16 /* TNodeType.Projection */) { // Current TNode represents an `` slot, thus it has no // DOM elements associated with it, so the **next sibling** node would // not be able to find an anchor. In this case, use full path instead. let nextTNode = tNode.next; // Skip over all `` slots in a row. while (nextTNode !== null && (nextTNode.type & 16 /* TNodeType.Projection */)) { nextTNode = nextTNode.next; } if (nextTNode && !isInSkipHydrationBlock(nextTNode)) { // Handle a tNode after the `` slot. appendSerializedNodePath(ngh, nextTNode, lView); } } else { // Handle cases where text nodes can be lost after DOM serialization: // 1. When there is an *empty text node* in DOM: in this case, this // node would not make it into the serialized string and as a result, // this node wouldn't be created in a browser. This would result in // a mismatch during the hydration, where the runtime logic would expect // a text node to be present in live DOM, but no text node would exist. // Example: `{{ name }}` when the `name` is an empty string. // This would result in `` string after serialization and // in a browser only the `span` element would be created. To resolve that, // an extra comment node is appended in place of an empty text node and // that special comment node is replaced with an empty text node *before* // hydration. // 2. When there are 2 consecutive text nodes present in the DOM. // Example: `
    Hello world
    `. // In this scenario, the live DOM would look like this: //
    #text('Hello ') #text('world') #comment('container')
    // Serialized string would look like this: `
    Hello world
    `. // The live DOM in a browser after that would be: //
    #text('Hello world') #comment('container')
    // Notice how 2 text nodes are now "merged" into one. This would cause hydration // logic to fail, since it'd expect 2 text nodes being present, not one. // To fix this, we insert a special comment node in between those text nodes, so // serialized representation is: `
    Hello world
    `. // This forces browser to create 2 text nodes separated by a comment node. // Before running a hydration process, this special comment node is removed, so the // live DOM has exactly the same state as it was before serialization. if (tNode.type & 1 /* TNodeType.Text */) { const rNode = unwrapRNode(lView[i]); // Collect this node as required special annotation only when its // contents is empty. Otherwise, such text node would be present on // the client after server-side rendering and no special handling needed. if (rNode.textContent === '') { context.corruptedTextNodes.set(rNode, "ngetn" /* TextNodeMarker.EmptyNode */); } else if (rNode.nextSibling?.nodeType === Node.TEXT_NODE) { context.corruptedTextNodes.set(rNode, "ngtns" /* TextNodeMarker.Separator */); } } if (tNode.projectionNext && tNode.projectionNext !== tNode.next && !isInSkipHydrationBlock(tNode.projectionNext)) { // Check if projection next is not the same as next, in which case // the node would not be found at creation time at runtime and we // need to provide a location for that node. appendSerializedNodePath(ngh, tNode.projectionNext, lView); } } } } return ngh; } /** * Determines whether a component instance that is represented * by a given LView uses `ViewEncapsulation.ShadowDom`. */ function componentUsesShadowDomEncapsulation(lView) { const instance = lView[CONTEXT]; return instance?.constructor ? getComponentDef(instance.constructor)?.encapsulation === ViewEncapsulation$1.ShadowDom : false; } /** * Annotates component host element for hydration: * - by either adding the `ngh` attribute and collecting hydration-related info * for the serialization and transferring to the client * - or by adding the `ngSkipHydration` attribute in case Angular detects that * component contents is not compatible with hydration. * * @param element The Host element to be annotated * @param lView The associated LView * @param context The hydration context */ function annotateHostElementForHydration(element, lView, context) { const renderer = lView[RENDERER]; if ((lView[FLAGS] & 32 /* LViewFlags.HasI18n */) === 32 /* LViewFlags.HasI18n */ || componentUsesShadowDomEncapsulation(lView)) { // Attach the skip hydration attribute if this component: // - either has i18n blocks, since hydrating such blocks is not yet supported // - or uses ShadowDom view encapsulation, since Domino doesn't support // shadow DOM, so we can not guarantee that client and server representations // would exactly match renderer.setAttribute(element, SKIP_HYDRATION_ATTR_NAME, ''); } else { const ngh = serializeLView(lView, context); const index = context.serializedViewCollection.add(ngh); renderer.setAttribute(element, NGH_ATTR_NAME, index.toString()); } } /** * Physically inserts the comment nodes to ensure empty text nodes and adjacent * text node separators are preserved after server serialization of the DOM. * These get swapped back for empty text nodes or separators once hydration happens * on the client. * * @param corruptedTextNodes The Map of text nodes to be replaced with comments * @param doc The document */ function insertCorruptedTextNodeMarkers(corruptedTextNodes, doc) { for (const [textNode, marker] of corruptedTextNodes) { textNode.after(doc.createComment(marker)); } } /** * Detects whether a given TNode represents a node that * is being content projected. */ function isContentProjectedNode(tNode) { let currentTNode = tNode; while (currentTNode != null) { // If we come across a component host node in parent nodes - // this TNode is in the content projection section. if (isComponentHost(currentTNode)) { return true; } currentTNode = currentTNode.parent; } return false; } /** * Check whether a given node exists, but is disconnected from the DOM. * * Note: we leverage the fact that we have this information available in the DOM emulation * layer (in Domino) for now. Longer-term solution should not rely on the DOM emulation and * only use internal data structures and state to compute this information. */ function isDisconnectedNode(tNode, lView) { return !(tNode.type & 16 /* TNodeType.Projection */) && !!lView[tNode.index] && !unwrapRNode(lView[tNode.index]).isConnected; } /** * Indicates whether the hydration-related code was added, * prevents adding it multiple times. */ let isHydrationSupportEnabled = false; /** * Defines a period of time that Angular waits for the `ApplicationRef.isStable` to emit `true`. * If there was no event with the `true` value during this time, Angular reports a warning. */ const APPLICATION_IS_STABLE_TIMEOUT = 10000; /** * Brings the necessary hydration code in tree-shakable manner. * The code is only present when the `provideClientHydration` is * invoked. Otherwise, this code is tree-shaken away during the * build optimization step. * * This technique allows us to swap implementations of methods so * tree shaking works appropriately when hydration is disabled or * enabled. It brings in the appropriate version of the method that * supports hydration only when enabled. */ function enableHydrationRuntimeSupport() { if (!isHydrationSupportEnabled) { isHydrationSupportEnabled = true; enableRetrieveHydrationInfoImpl(); enableLocateOrCreateElementNodeImpl(); enableLocateOrCreateTextNodeImpl(); enableLocateOrCreateElementContainerNodeImpl(); enableLocateOrCreateContainerAnchorImpl(); enableLocateOrCreateContainerRefImpl(); enableFindMatchingDehydratedViewImpl(); enableApplyRootElementTransformImpl(); } } /** * Detects whether the code is invoked in a browser. * Later on, this check should be replaced with a tree-shakable * flag (e.g. `!isServer`). */ function isBrowser() { return inject(PLATFORM_ID) === 'browser'; } /** * Outputs a message with hydration stats into a console. */ function printHydrationStats(injector) { const console = injector.get(Console); const message = `Angular hydrated ${ngDevMode.hydratedComponents} component(s) ` + `and ${ngDevMode.hydratedNodes} node(s), ` + `${ngDevMode.componentsSkippedHydration} component(s) were skipped. ` + `Note: this feature is in Developer Preview mode. ` + `Learn more at https://angular.io/guide/hydration.`; // tslint:disable-next-line:no-console console.log(message); } /** * Returns a Promise that is resolved when an application becomes stable. */ function whenStable(appRef, injector) { const isStablePromise = appRef.isStable.pipe(first((isStable) => isStable)).toPromise(); if (typeof ngDevMode !== 'undefined' && ngDevMode) { const timeoutTime = APPLICATION_IS_STABLE_TIMEOUT; const console = injector.get(Console); const ngZone = injector.get(NgZone); // The following call should not and does not prevent the app to become stable // We cannot use RxJS timer here because the app would remain unstable. // This also avoids an extra change detection cycle. const timeoutId = ngZone.runOutsideAngular(() => { return setTimeout(() => logWarningOnStableTimedout(timeoutTime, console), timeoutTime); }); isStablePromise.finally(() => clearTimeout(timeoutId)); } return isStablePromise.then(() => { }); } /** * Returns a set of providers required to setup hydration support * for an application that is server side rendered. This function is * included into the `provideClientHydration` public API function from * the `platform-browser` package. * * The function sets up an internal flag that would be recognized during * the server side rendering time as well, so there is no need to * configure or change anything in NgUniversal to enable the feature. */ function withDomHydration() { return makeEnvironmentProviders([ { provide: IS_HYDRATION_DOM_REUSE_ENABLED, useFactory: () => { let isEnabled = true; if (isBrowser()) { // On the client, verify that the server response contains // hydration annotations. Otherwise, keep hydration disabled. const transferState = inject(TransferState, { optional: true }); isEnabled = !!transferState?.get(NGH_DATA_KEY, null); if (!isEnabled && (typeof ngDevMode !== 'undefined' && ngDevMode)) { const console = inject(Console); const message = formatRuntimeError(-505 /* RuntimeErrorCode.MISSING_HYDRATION_ANNOTATIONS */, 'Angular hydration was requested on the client, but there was no ' + 'serialized information present in the server response, ' + 'thus hydration was not enabled. ' + 'Make sure the `provideClientHydration()` is included into the list ' + 'of providers in the server part of the application configuration.'); // tslint:disable-next-line:no-console console.warn(message); } } if (isEnabled) { inject(ENABLED_SSR_FEATURES).add('hydration'); } return isEnabled; }, }, { provide: ENVIRONMENT_INITIALIZER, useValue: () => { // Since this function is used across both server and client, // make sure that the runtime code is only added when invoked // on the client. Moving forward, the `isBrowser` check should // be replaced with a tree-shakable alternative (e.g. `isServer` // flag). if (isBrowser() && inject(IS_HYDRATION_DOM_REUSE_ENABLED)) { enableHydrationRuntimeSupport(); } }, multi: true, }, { provide: PRESERVE_HOST_CONTENT, useFactory: () => { // Preserve host element content only in a browser // environment and when hydration is configured properly. // On a server, an application is rendered from scratch, // so the host content needs to be empty. return isBrowser() && inject(IS_HYDRATION_DOM_REUSE_ENABLED); } }, { provide: APP_BOOTSTRAP_LISTENER, useFactory: () => { if (isBrowser() && inject(IS_HYDRATION_DOM_REUSE_ENABLED)) { const appRef = inject(ApplicationRef); const injector = inject(Injector); return () => { whenStable(appRef, injector).then(() => { // Wait until an app becomes stable and cleanup all views that // were not claimed during the application bootstrap process. // The timing is similar to when we start the serialization process // on the server. cleanupDehydratedViews(appRef); if (typeof ngDevMode !== 'undefined' && ngDevMode) { printHydrationStats(injector); } }); }; } return () => { }; // noop }, multi: true, } ]); } /** * * @param time The time in ms until the stable timedout warning message is logged */ function logWarningOnStableTimedout(time, console) { const message = `Angular hydration expected the ApplicationRef.isStable() to emit \`true\`, but it ` + `didn't happen within ${time}ms. Angular hydration logic depends on the application becoming stable ` + `as a signal to complete hydration process.`; console.warn(formatRuntimeError(-506 /* RuntimeErrorCode.HYDRATION_STABLE_TIMEDOUT */, message)); } /** Coerces a value (typically a string) to a boolean. */ function coerceToBoolean(value) { return typeof value === 'boolean' ? value : (value != null && value !== 'false'); } /** * Compiles a partial directive declaration object into a full directive definition object. * * @codeGenApi */ function ɵɵngDeclareDirective(decl) { const compiler = getCompilerFacade({ usage: 1 /* JitCompilerUsage.PartialDeclaration */, kind: 'directive', type: decl.type }); return compiler.compileDirectiveDeclaration(angularCoreEnv, `ng:///${decl.type.name}/ɵfac.js`, decl); } /** * Evaluates the class metadata declaration. * * @codeGenApi */ function ɵɵngDeclareClassMetadata(decl) { setClassMetadata(decl.type, decl.decorators, decl.ctorParameters ?? null, decl.propDecorators ?? null); } /** * Compiles a partial component declaration object into a full component definition object. * * @codeGenApi */ function ɵɵngDeclareComponent(decl) { const compiler = getCompilerFacade({ usage: 1 /* JitCompilerUsage.PartialDeclaration */, kind: 'component', type: decl.type }); return compiler.compileComponentDeclaration(angularCoreEnv, `ng:///${decl.type.name}/ɵcmp.js`, decl); } /** * Compiles a partial pipe declaration object into a full pipe definition object. * * @codeGenApi */ function ɵɵngDeclareFactory(decl) { const compiler = getCompilerFacade({ usage: 1 /* JitCompilerUsage.PartialDeclaration */, kind: getFactoryKind(decl.target), type: decl.type }); return compiler.compileFactoryDeclaration(angularCoreEnv, `ng:///${decl.type.name}/ɵfac.js`, decl); } function getFactoryKind(target) { switch (target) { case FactoryTarget.Directive: return 'directive'; case FactoryTarget.Component: return 'component'; case FactoryTarget.Injectable: return 'injectable'; case FactoryTarget.Pipe: return 'pipe'; case FactoryTarget.NgModule: return 'NgModule'; } } /** * Compiles a partial injectable declaration object into a full injectable definition object. * * @codeGenApi */ function ɵɵngDeclareInjectable(decl) { const compiler = getCompilerFacade({ usage: 1 /* JitCompilerUsage.PartialDeclaration */, kind: 'injectable', type: decl.type }); return compiler.compileInjectableDeclaration(angularCoreEnv, `ng:///${decl.type.name}/ɵprov.js`, decl); } /** * Compiles a partial injector declaration object into a full injector definition object. * * @codeGenApi */ function ɵɵngDeclareInjector(decl) { const compiler = getCompilerFacade({ usage: 1 /* JitCompilerUsage.PartialDeclaration */, kind: 'NgModule', type: decl.type }); return compiler.compileInjectorDeclaration(angularCoreEnv, `ng:///${decl.type.name}/ɵinj.js`, decl); } /** * Compiles a partial NgModule declaration object into a full NgModule definition object. * * @codeGenApi */ function ɵɵngDeclareNgModule(decl) { const compiler = getCompilerFacade({ usage: 1 /* JitCompilerUsage.PartialDeclaration */, kind: 'NgModule', type: decl.type }); return compiler.compileNgModuleDeclaration(angularCoreEnv, `ng:///${decl.type.name}/ɵmod.js`, decl); } /** * Compiles a partial pipe declaration object into a full pipe definition object. * * @codeGenApi */ function ɵɵngDeclarePipe(decl) { const compiler = getCompilerFacade({ usage: 1 /* JitCompilerUsage.PartialDeclaration */, kind: 'pipe', type: decl.type }); return compiler.compilePipeDeclaration(angularCoreEnv, `ng:///${decl.type.name}/ɵpipe.js`, decl); } // clang-format off // clang-format on // clang-format off // clang-format on // This file exists to allow the set of reactivity exports to be modified in g3, as these APIs are /** * Creates a `ComponentRef` instance based on provided component type and a set of options. * * @usageNotes * * The example below demonstrates how the `createComponent` function can be used * to create an instance of a ComponentRef dynamically and attach it to an ApplicationRef, * so that it gets included into change detection cycles. * * Note: the example uses standalone components, but the function can also be used for * non-standalone components (declared in an NgModule) as well. * * ```typescript * @Component({ * standalone: true, * template: `Hello {{ name }}!` * }) * class HelloComponent { * name = 'Angular'; * } * * @Component({ * standalone: true, * template: `
    ` * }) * class RootComponent {} * * // Bootstrap an application. * const applicationRef = await bootstrapApplication(RootComponent); * * // Locate a DOM node that would be used as a host. * const host = document.getElementById('hello-component-host'); * * // Get an `EnvironmentInjector` instance from the `ApplicationRef`. * const environmentInjector = applicationRef.injector; * * // We can now create a `ComponentRef` instance. * const componentRef = createComponent(HelloComponent, {host, environmentInjector}); * * // Last step is to register the newly created ref using the `ApplicationRef` instance * // to include the component view into change detection cycles. * applicationRef.attachView(componentRef.hostView); * ``` * * @param component Component class reference. * @param options Set of options to use: * * `environmentInjector`: An `EnvironmentInjector` instance to be used for the component, see * additional info about it [here](/guide/standalone-components#environment-injectors). * * `hostElement` (optional): A DOM node that should act as a host node for the component. If not * provided, Angular creates one based on the tag name used in the component selector (and falls * back to using `div` if selector doesn't have tag name info). * * `elementInjector` (optional): An `ElementInjector` instance, see additional info about it * [here](/guide/hierarchical-dependency-injection#elementinjector). * * `projectableNodes` (optional): A list of DOM nodes that should be projected through * [``](api/core/ng-content) of the new component instance. * @returns ComponentRef instance that represents a given Component. * * @publicApi */ function createComponent(component, options) { ngDevMode && assertComponentDef(component); const componentDef = getComponentDef(component); const elementInjector = options.elementInjector || getNullInjector(); const factory = new ComponentFactory(componentDef); return factory.create(elementInjector, options.projectableNodes, options.hostElement, options.environmentInjector); } /** * Creates an object that allows to retrieve component metadata. * * @usageNotes * * The example below demonstrates how to use the function and how the fields * of the returned object map to the component metadata. * * ```typescript * @Component({ * standalone: true, * selector: 'foo-component', * template: ` * * * `, * }) * class FooComponent { * @Input('inputName') inputPropName: string; * @Output('outputName') outputPropName = new EventEmitter(); * } * * const mirror = reflectComponentType(FooComponent); * expect(mirror.type).toBe(FooComponent); * expect(mirror.selector).toBe('foo-component'); * expect(mirror.isStandalone).toBe(true); * expect(mirror.inputs).toEqual([{propName: 'inputName', templateName: 'inputPropName'}]); * expect(mirror.outputs).toEqual([{propName: 'outputName', templateName: 'outputPropName'}]); * expect(mirror.ngContentSelectors).toEqual([ * '*', // first `` in a template, the selector defaults to `*` * 'content-selector-a' // second `` in a template * ]); * ``` * * @param component Component class reference. * @returns An object that allows to retrieve component metadata. * * @publicApi */ function reflectComponentType(component) { const componentDef = getComponentDef(component); if (!componentDef) return null; const factory = new ComponentFactory(componentDef); return { get selector() { return factory.selector; }, get type() { return factory.componentType; }, get inputs() { return factory.inputs; }, get outputs() { return factory.outputs; }, get ngContentSelectors() { return factory.ngContentSelectors; }, get isStandalone() { return componentDef.standalone; }, }; } /** * Merge multiple application configurations from left to right. * * @param configs Two or more configurations to be merged. * @returns A merged [ApplicationConfig](api/core/ApplicationConfig). * * @publicApi */ function mergeApplicationConfig(...configs) { return configs.reduce((prev, curr) => { return Object.assign(prev, curr, { providers: [...prev.providers, ...curr.providers] }); }, { providers: [] }); } /** * @module * @description * Entry point from which you should import all public core APIs. */ if (typeof ngDevMode !== 'undefined' && ngDevMode) { // This helper is to give a reasonable error message to people upgrading to v9 that have not yet // installed `@angular/localize` in their app. // tslint:disable-next-line: no-toplevel-property-access _global.$localize = _global.$localize || function () { throw new Error('It looks like your application or one of its dependencies is using i18n.\n' + 'Angular 9 introduced a global `$localize()` function that needs to be loaded.\n' + 'Please run `ng add @angular/localize` from the Angular CLI.\n' + '(For non-CLI projects, add `import \'@angular/localize/init\';` to your `polyfills.ts` file.\n' + 'For server-side rendering applications add the import to your `main.server.ts` file.)'); }; } }) }; })); //# sourceMappingURL=angular-core.js.map