/* 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
*
* ```
*
* 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 `