refactor(core): improve code around element validation (#46175)

improve code regarding element validation by creating a new file
exporting validation functions and not exporting utils previously
globally available

PR Close #46175
This commit is contained in:
dario-piotrowicz 2022-05-28 14:31:59 +01:00 committed by Alex Rickabaugh
parent 5cfde8b23e
commit d846bba678
5 changed files with 306 additions and 290 deletions

View file

@ -50,4 +50,4 @@ export * from './style_map_interpolation';
export * from './style_prop_interpolation';
export * from './host_property';
export * from './i18n';
export {ɵgetUnknownPropertyStrictMode, ɵsetUnknownPropertyStrictMode} from './shared';
export {ɵgetUnknownElementStrictMode, ɵsetUnknownElementStrictMode, ɵgetUnknownPropertyStrictMode, ɵsetUnknownPropertyStrictMode} from './element_validation';

View file

@ -6,8 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import {formatRuntimeError, RuntimeError, RuntimeErrorCode} from '../../errors';
import {SchemaMetadata} from '../../metadata/schema';
import {assertDefined, assertEqual, assertIndexInRange} from '../../util/assert';
import {assertFirstCreatePass, assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery';
@ -23,26 +21,10 @@ import {computeStaticStyling} from '../styling/static_styling';
import {setUpAttributes} from '../util/attrs_utils';
import {getConstant} from '../util/view_utils';
import {validateElementIsKnown} from './element_validation';
import {setDirectiveInputsWhichShadowsStyling} from './property';
import {createDirectivesInstances, executeContentQueries, getOrCreateTNode, getTemplateLocationDetails, isHostComponentStandalone, matchingSchemas, resolveDirectives, saveResolvedLocalsInData} from './shared';
import {createDirectivesInstances, executeContentQueries, getOrCreateTNode, resolveDirectives, saveResolvedLocalsInData} from './shared';
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).
*/
export function ɵsetUnknownElementStrictMode(shouldThrow: boolean) {
shouldThrowErrorOnUnknownElement = shouldThrow;
}
/**
* Gets the current value of the strict mode.
*/
export function ɵgetUnknownElementStrictMode() {
return shouldThrowErrorOnUnknownElement;
}
function elementStartFirstCreatePass(
index: number, tView: TView, lView: LView, native: RElement, name: string,
@ -208,69 +190,3 @@ export function ɵɵelement(
ɵɵelementEnd();
return ɵɵelement;
}
/**
* 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: RElement, lView: LView, tagName: string|null, schemas: SchemaMetadata[]|null,
hasDirectives: boolean): void {
// 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 while most browsers return 'function', IE returns 'object'.
(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(RuntimeErrorCode.UNKNOWN_ELEMENT, message);
} else {
console.error(formatRuntimeError(RuntimeErrorCode.UNKNOWN_ELEMENT, message));
}
}
}
}

View file

@ -0,0 +1,298 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {formatRuntimeError, RuntimeError, RuntimeErrorCode} from '../../errors';
import {Type} from '../../interface/type';
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema';
import {throwError} from '../../util/assert';
import {getComponentDef} from '../definition';
import {ComponentDef} from '../interfaces/definition';
import {TNodeType} from '../interfaces/node';
import {RComment, RElement} from '../interfaces/renderer_dom';
import {CONTEXT, DECLARATION_COMPONENT_VIEW, LView} from '../interfaces/view';
import {isAnimationProp} from '../util/attrs_utils';
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).
*/
export function ɵsetUnknownElementStrictMode(shouldThrow: boolean) {
shouldThrowErrorOnUnknownElement = shouldThrow;
}
/**
* Gets the current value of the strict mode.
*/
export 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).
*/
export function ɵsetUnknownPropertyStrictMode(shouldThrow: boolean) {
shouldThrowErrorOnUnknownProperty = shouldThrow;
}
/**
* Gets the current value of the strict mode.
*/
export 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
*/
export function validateElementIsKnown(
element: RElement, lView: LView, tagName: string|null, schemas: SchemaMetadata[]|null,
hasDirectives: boolean): void {
// 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 while most browsers return 'function', IE returns 'object'.
(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(RuntimeErrorCode.UNKNOWN_ELEMENT, message);
} else {
console.error(formatRuntimeError(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
*/
export function isPropertyValid(
element: RElement|RComment, propName: string, tagName: string|null,
schemas: SchemaMetadata[]|null): boolean {
// 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, and we are in a browser context (web worker nodes should be skipped).
if (matchingSchemas(schemas, tagName) || propName in element || isAnimationProp(propName)) {
return true;
}
// Note: `typeof Node` returns 'function' in most browsers, but on IE it is 'object' so we
// need to account for both here, while being careful with `typeof null` also returning 'object'.
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
*/
export function handleUnknownPropertyError(
propName: string, tagName: string|null, nodeType: TNodeType, lView: LView): void {
// Special-case a situation when a structural directive is applied to
// an `<ng-template>` element, for example: `<ng-template *ngIf="true">`.
// 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 === 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 `CommonModule` is not imported.
message += `\nIf the '${propName}' is an Angular control flow directive, ` +
`please make sure that 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.`;
}
}
if (shouldThrowErrorOnUnknownProperty) {
throw new RuntimeError(RuntimeErrorCode.UNKNOWN_BINDING, message);
} else {
console.error(formatRuntimeError(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: LView): ComponentDef<unknown>|null {
!ngDevMode && throwError('Must never be called in production mode');
const declarationLView = lView[DECLARATION_COMPONENT_VIEW] as LView<Type<unknown>>;
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: LView): boolean {
!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: LView): string {
!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.
* We use this set to produce a more precises error message with a note
* that the `CommonModule` should also be included.
*/
export const KNOWN_CONTROL_FLOW_DIRECTIVES =
new Set(['ngIf', 'ngFor', 'ngSwitch', 'ngSwitchCase', 'ngSwitchDefault']);
/**
* Returns true if the tag name is allowed by specified schemas.
* @param schemas Array of schemas
* @param tagName Name of the tag
*/
export function matchingSchemas(schemas: SchemaMetadata[]|null, tagName: string|null): boolean {
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;
}

View file

@ -20,7 +20,6 @@ import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/
import {stringify} from '../../util/stringify';
import {assertFirstCreatePass, assertFirstUpdatePass, assertLContainer, assertLView, assertTNodeForLView, assertTNodeForTView} from '../assert';
import {attachPatchData, readPatchedLView} from '../context_discovery';
import {getComponentDef} from '../definition';
import {getFactoryDef} from '../definition_factory';
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
import {throwMultipleComponentError} from '../errors';
@ -49,26 +48,9 @@ import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, isCreation
import {selectIndexInternal} from './advance';
import {ɵɵdirectiveInject} from './di';
import {handleUnknownPropertyError, isPropertyValid, matchingSchemas} from './element_validation';
import {attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor} from './lview_debug';
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).
*/
export function ɵsetUnknownPropertyStrictMode(shouldThrow: boolean) {
shouldThrowErrorOnUnknownProperty = shouldThrow;
}
/**
* Gets the current value of the strict mode.
*/
export function ɵgetUnknownPropertyStrictMode() {
return shouldThrowErrorOnUnknownProperty;
}
/**
* A permanent marker promise which signifies that the current CD tree is
* clean.
@ -268,62 +250,6 @@ export function createTNodeAtIndex(
return tNode;
}
/**
* 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: LView): ComponentDef<unknown>|null {
!ngDevMode && throwError('Must never be called in production mode');
const declarationLView = lView[DECLARATION_COMPONENT_VIEW] as LView<Type<unknown>>;
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.
*/
export function isHostComponentStandalone(lView: LView): boolean {
!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.
*/
export function getTemplateLocationDetails(lView: LView): string {
!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)` : '';
}
/**
* When elements are created dynamically after a view blueprint is created (e.g. through
* i18nApply()), we need to adjust the blueprint for future
@ -1089,10 +1015,8 @@ export function elementPropertyInternal<T>(
if (ngDevMode) {
validateAgainstEventProperties(propName);
if (!validateProperty(element, tNode.value, propName, tView.schemas)) {
// Return here since we only log warnings for unknown properties.
handleUnknownPropertyError(propName, tNode, lView);
return;
if (!isPropertyValid(element, propName, tNode.value, tView.schemas)) {
handleUnknownPropertyError(propName, tNode.value, tNode.type, lView);
}
ngDevMode.rendererSetProperty++;
}
@ -1110,7 +1034,7 @@ export function elementPropertyInternal<T>(
// If the node is a container and the property didn't
// match any of the inputs or schemas we should throw.
if (ngDevMode && !matchingSchemas(tView.schemas, tNode.value)) {
handleUnknownPropertyError(propName, tNode, lView);
handleUnknownPropertyError(propName, tNode.value, tNode.type, lView);
}
}
}
@ -1167,128 +1091,6 @@ export function setNgReflectProperties(
}
}
/**
* 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 tagName Name of the tag to check
* @param propName Name of the property to check
* @param schemas Array of schemas
*/
function validateProperty(
element: RElement|RComment, tagName: string|null, propName: string,
schemas: SchemaMetadata[]|null): boolean {
// 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, and we are in a browser context (web worker nodes should be skipped).
if (matchingSchemas(schemas, tagName) || propName in element || isAnimationProp(propName)) {
return true;
}
// Note: `typeof Node` returns 'function' in most browsers, but on IE it is 'object' so we
// need to account for both here, while being careful with `typeof null` also returning 'object'.
return typeof Node === 'undefined' || Node === null || !(element instanceof Node);
}
/**
* Returns true if the tag name is allowed by specified schemas.
* @param schemas Array of schemas
* @param tagName Name of the tag
*/
export function matchingSchemas(schemas: SchemaMetadata[]|null, tagName: string|null): boolean {
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 set of known control flow directives.
* We use this set to produce a more precises error message with a note
* that the `CommonModule` should also be included.
*/
export const KNOWN_CONTROL_FLOW_DIRECTIVES =
new Set(['ngIf', 'ngFor', 'ngSwitch', 'ngSwitchCase', 'ngSwitchDefault']);
/**
* Logs or throws an error that a property is not supported on an element.
*
* @param propName Name of the invalid property.
* @param tNode A `TNode` that represents a current component that is being rendered.
* @param lView An `LView` that represents a current component that is being rendered.
*/
function handleUnknownPropertyError(propName: string, tNode: TNode, lView: LView): void {
let tagName = tNode.value;
// Special-case a situation when a structural directive is applied to
// an `<ng-template>` element, for example: `<ng-template *ngIf="true">`.
// 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 && tNode.type === 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 `CommonModule` is not imported.
message += `\nIf the '${propName}' is an Angular control flow directive, ` +
`please make sure that 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.`;
}
}
if (shouldThrowErrorOnUnknownProperty) {
throw new RuntimeError(RuntimeErrorCode.UNKNOWN_BINDING, message);
} else {
console.error(formatRuntimeError(RuntimeErrorCode.UNKNOWN_BINDING, message));
}
}
/**
* Instantiate a root component.
*/

View file

@ -8,7 +8,7 @@
import {CommonModule} from '@angular/common';
import {Component, createNgModuleRef, CUSTOM_ELEMENTS_SCHEMA, destroyPlatform, Directive, Injectable, InjectionToken, NgModule, NgModuleRef, NO_ERRORS_SCHEMA, Pipe, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelement as element, ɵɵproperty as property} from '@angular/core';
import {KNOWN_CONTROL_FLOW_DIRECTIVES} from '@angular/core/src/render3/instructions/shared';
import {KNOWN_CONTROL_FLOW_DIRECTIVES} from '@angular/core/src/render3/instructions/element_validation';
import {TestBed} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';