From 24d63cd0b3681ea284fe6526b1510f4e79f3c739 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Fri, 18 Nov 2022 10:11:29 +0100 Subject: [PATCH] feat(core): Make the `isStandalone()` function available in public API This commit updates an internal `isStandalone` function and exposes it as a public API, so that it can be used in applications code. fixes #47919 --- goldens/public-api/core/errors.md | 2 + goldens/public-api/core/index.md | 3 + packages/core/src/core.ts | 1 + .../core/src/core_render3_private_export.ts | 1 - packages/core/src/errors.ts | 1 + packages/core/src/render3/definition.ts | 25 ++++++- packages/core/src/render3/jit/module.ts | 5 +- .../core/test/acceptance/standalone_spec.ts | 72 ++++++++++++++++++- .../animations/bundle.golden_symbols.json | 6 ++ .../cyclic_import/bundle.golden_symbols.json | 6 ++ .../forms_reactive/bundle.golden_symbols.json | 6 ++ .../bundle.golden_symbols.json | 6 ++ .../hello_world/bundle.golden_symbols.json | 12 ++++ .../router/bundle.golden_symbols.json | 3 + .../bundle.golden_symbols.json | 6 ++ .../bundling/todo/bundle.golden_symbols.json | 6 ++ packages/router/src/utils/config.ts | 2 +- 17 files changed, 157 insertions(+), 6 deletions(-) diff --git a/goldens/public-api/core/errors.md b/goldens/public-api/core/errors.md index 65712aab89c58f..f364f1730e8dac 100644 --- a/goldens/public-api/core/errors.md +++ b/goldens/public-api/core/errors.md @@ -77,6 +77,8 @@ export const enum RuntimeErrorCode { // (undocumented) NO_SUPPORTING_DIFFER_FACTORY = 901, // (undocumented) + NON_ANNOTATED_CLASS = 911, + // (undocumented) PIPE_NOT_FOUND = -302, // (undocumented) PLATFORM_ALREADY_DESTROYED = 404, diff --git a/goldens/public-api/core/index.md b/goldens/public-api/core/index.md index 93b8d269dafead..e7202277ace602 100644 --- a/goldens/public-api/core/index.md +++ b/goldens/public-api/core/index.md @@ -787,6 +787,9 @@ export interface InputDecorator { // @public export function isDevMode(): boolean; +// @public +export function isStandalone(type: Type): boolean; + // @public export interface IterableChangeRecord { readonly currentIndex: number | null; diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 5a698caa3eabbc..ac1fe45cef8db9 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -38,6 +38,7 @@ export {SecurityContext} from './sanitization/security'; export {Sanitizer} from './sanitization/sanitizer'; export {createNgModule, createNgModuleRef, createEnvironmentInjector} from './render3/ng_module_ref'; export {createComponent, reflectComponentType, ComponentMirror} from './render3/component'; +export {isStandalone} from './render3/definition'; import {global} from './util/global'; if (typeof ngDevMode !== 'undefined' && ngDevMode) { diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 84ecfdd973aa7c..b9832acda5e9aa 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -248,7 +248,6 @@ export { export { compilePipe as ɵcompilePipe, } from './render3/jit/pipe'; -export { isStandalone as ɵisStandalone} from './render3/definition'; export { Profiler as ɵProfiler, ProfilerEvent as ɵProfilerEvent } from './render3/profiler'; export { publishDefaultGlobalUtils as ɵpublishDefaultGlobalUtils diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts index 00f9f77ac56396..218a884217f09b 100644 --- a/packages/core/src/errors.ts +++ b/packages/core/src/errors.ts @@ -83,6 +83,7 @@ export const enum RuntimeErrorCode { MISSING_ZONEJS = 908, UNEXPECTED_ZONE_STATE = 909, UNSAFE_IFRAME_ATTRS = -910, + UNEXPECTED_CLASS_TYPE = 911, } /** diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 7d216319108e34..aaaa280366c78e 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -7,7 +7,7 @@ */ import {ChangeDetectionStrategy} from '../change_detection/constants'; -import {NG_PROV_DEF} from '../di/interface/defs'; +import {RuntimeError, RuntimeErrorCode} from '../errors'; import {Mutable, Type} from '../interface/type'; import {NgModuleDef} from '../metadata/ng_module_def'; import {SchemaMetadata} from '../metadata/schema'; @@ -21,6 +21,7 @@ import {NG_COMP_DEF, NG_DIR_DEF, NG_MOD_DEF, NG_PIPE_DEF} from './fields'; import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, ContentQueriesFunction, DependencyTypeList, DirectiveDef, DirectiveDefFeature, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList, TypeOrFactory, ViewQueriesFunction} from './interfaces/definition'; import {TAttributes, TConstantsOrFactory} from './interfaces/node'; import {CssSelectorList} from './interfaces/projection'; +import {stringifyForError} from './util/stringify_utils'; /** Counter used to generate unique IDs for component definitions. */ @@ -741,9 +742,29 @@ export function getPipeDef(type: any): PipeDef|null { return type[NG_PIPE_DEF] || null; } +/** + * Checks whether a given Component, Directive or Pipe is marked as standalone. + * See this guide for additional information: https://angular.io/guide/standalone-components + * + * @param type A reference to a Component, Directive or Pipe. + * @publicApi + */ export function isStandalone(type: Type): boolean { + if (getNgModuleDef(type)) { + return false; + } + const def = getComponentDef(type) || getDirectiveDef(type) || getPipeDef(type); - return def !== null ? def.standalone : false; + if (ngDevMode && def === null) { + throw new RuntimeError( + RuntimeErrorCode.UNEXPECTED_CLASS_TYPE, + `The \`isStandalone\` function supports Components, Directives and Pipes as an argument. ` + + `Make sure that the ${ + stringifyForError(type)} class is annotated with the \`@Component\`, ` + + `\`@Directive\` or \`@Pipe\` decorator.`); + } + + return def ? def.standalone : false; } export function getNgModuleDef(type: any, throwNotFound: true): NgModuleDef; diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index 8b511262440b6e..723b54f8978e52 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -210,7 +210,10 @@ function verifySemanticsOfNgModuleDef( if (verifiedNgModule.get(moduleType)) return; // skip verifications of standalone components, directives, and pipes - if (isStandalone(moduleType)) return; + if ((getComponentDef(moduleType) || getPipeDef(moduleType) || getDirectiveDef(moduleType)) && + isStandalone(moduleType)) { + return; + } verifiedNgModule.set(moduleType, true); moduleType = resolveForwardRef(moduleType); diff --git a/packages/core/test/acceptance/standalone_spec.ts b/packages/core/test/acceptance/standalone_spec.ts index 7f3057848492a2..25296a7d0571e4 100644 --- a/packages/core/test/acceptance/standalone_spec.ts +++ b/packages/core/test/acceptance/standalone_spec.ts @@ -7,7 +7,8 @@ */ import {CommonModule} from '@angular/common'; -import {Component, createEnvironmentInjector, Directive, EnvironmentInjector, forwardRef, Injector, Input, NgModule, NO_ERRORS_SCHEMA, OnInit, Pipe, PipeTransform, ViewChild, ViewContainerRef} from '@angular/core'; +import {Component, createEnvironmentInjector, Directive, EnvironmentInjector, forwardRef, Injector, Input, isStandalone, NgModule, NO_ERRORS_SCHEMA, OnInit, Pipe, PipeTransform, ViewChild, ViewContainerRef} from '@angular/core'; +import {RuntimeErrorCode} from '@angular/core/src/errors'; import {TestBed} from '@angular/core/testing'; describe('standalone components, directives, and pipes', () => { @@ -847,4 +848,73 @@ describe('standalone components, directives, and pipes', () => { expect(fixture.nativeElement.textContent).toBe(''); }); }); + + describe('isStandalone()', () => { + it('should return `true` if component is standalone', () => { + @Component({selector: 'standalone-cmp', standalone: true}) + class StandaloneCmp { + } + + expect(isStandalone(StandaloneCmp)).toBeTrue(); + }); + + it('should return `false` if component is not standalone', () => { + @Component({selector: 'standalone-cmp', standalone: false}) + class StandaloneCmp { + } + + expect(isStandalone(StandaloneCmp)).toBeFalse(); + }); + + it('should return `true` if directive is standalone', () => { + @Directive({selector: '[standaloneDir]', standalone: true}) + class StandAloneDirective { + } + + expect(isStandalone(StandAloneDirective)).toBeTrue(); + }); + + it('should return `false` if directive is standalone', () => { + @Directive({selector: '[standaloneDir]', standalone: false}) + class StandAloneDirective { + } + + expect(isStandalone(StandAloneDirective)).toBeFalse(); + }); + + it('should return `true` if pipe is standalone', () => { + @Pipe({name: 'standalonePipe', standalone: true}) + class StandAlonePipe { + } + + expect(isStandalone(StandAlonePipe)).toBeTrue(); + }); + + it('should return `false` if pipe is standalone', () => { + @Pipe({name: 'standalonePipe', standalone: false}) + class StandAlonePipe { + } + + expect(isStandalone(StandAlonePipe)).toBeFalse(); + }); + + it('should throw when a non-annotated class is used as an argument', () => { + function getErrorMessageRegexp() { + const errorMessagePart = + 'NG0' + Math.abs(RuntimeErrorCode.UNEXPECTED_CLASS_TYPE).toString(); + return new RegExp(errorMessagePart); + } + + class NonAnnotatedClass {} + expect(() => isStandalone(NonAnnotatedClass)).toThrowError(getErrorMessageRegexp()); + }); + + it('should check wether an NgModule return false', () => { + @NgModule({}) + class NgModuleA { + } + + expect(isStandalone(NgModuleA)).toBeFalse(); + }); + }); }); diff --git a/packages/core/test/bundling/animations/bundle.golden_symbols.json b/packages/core/test/bundling/animations/bundle.golden_symbols.json index 8428e0ae89bbd0..45d6d4af254ff6 100644 --- a/packages/core/test/bundling/animations/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations/bundle.golden_symbols.json @@ -842,6 +842,9 @@ { "name": "getNextLContainer" }, + { + "name": "getNgModuleDef" + }, { "name": "getNodeInjectable" }, @@ -1310,6 +1313,9 @@ { "name": "stringifyCSSSelector" }, + { + "name": "stringifyForError" + }, { "name": "style" }, diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index eab3aa9ebf4d83..b520e700cc9468 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -620,6 +620,9 @@ { "name": "getNextLContainer" }, + { + "name": "getNgModuleDef" + }, { "name": "getNodeInjectable" }, @@ -989,6 +992,9 @@ { "name": "stringifyCSSSelector" }, + { + "name": "stringifyForError" + }, { "name": "subscribeTo" }, diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index 85d3f57fda9646..8744efc6e3b911 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -893,6 +893,9 @@ { "name": "getNextLContainer" }, + { + "name": "getNgModuleDef" + }, { "name": "getNodeInjectable" }, @@ -1466,6 +1469,9 @@ { "name": "stringifyCSSSelector" }, + { + "name": "stringifyForError" + }, { "name": "subscribeTo" }, diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index 322393d2c42a75..8e708e59e86f07 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -854,6 +854,9 @@ { "name": "getNextLContainer" }, + { + "name": "getNgModuleDef" + }, { "name": "getNodeInjectable" }, @@ -1442,6 +1445,9 @@ { "name": "stringifyCSSSelector" }, + { + "name": "stringifyForError" + }, { "name": "subscribeTo" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index c48039ad2d8ee1..60e766f95fbf7f 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -113,6 +113,9 @@ { "name": "NG_COMP_DEF" }, + { + "name": "NG_DIR_DEF" + }, { "name": "NG_ELEMENT_ID" }, @@ -131,6 +134,9 @@ { "name": "NG_MOD_DEF" }, + { + "name": "NG_PIPE_DEF" + }, { "name": "NG_PROV_DEF" }, @@ -458,6 +464,9 @@ { "name": "getNextLContainer" }, + { + "name": "getNgModuleDef" + }, { "name": "getNodeInjectable" }, @@ -749,6 +758,9 @@ { "name": "stringifyCSSSelector" }, + { + "name": "stringifyForError" + }, { "name": "subscribeTo" }, diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 5f562fbfc9fe18..fa5ccf0149b833 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -1763,6 +1763,9 @@ { "name": "stringifyCSSSelector" }, + { + "name": "stringifyForError" + }, { "name": "stripTrailingSlash" }, diff --git a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json index 3396a30561780e..c40ea4b20617b6 100644 --- a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json +++ b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json @@ -176,6 +176,9 @@ { "name": "NG_INJ_DEF" }, + { + "name": "NG_MOD_DEF" + }, { "name": "NG_PIPE_DEF" }, @@ -842,6 +845,9 @@ { "name": "stringifyCSSSelector" }, + { + "name": "stringifyForError" + }, { "name": "subscribeTo" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 6b32a8b8403559..c0c877fc335c63 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -749,6 +749,9 @@ { "name": "getNextLContainer" }, + { + "name": "getNgModuleDef" + }, { "name": "getNodeInjectable" }, @@ -1223,6 +1226,9 @@ { "name": "stringifyCSSSelector" }, + { + "name": "stringifyForError" + }, { "name": "subscribeTo" }, diff --git a/packages/router/src/utils/config.ts b/packages/router/src/utils/config.ts index 5e674df8ae0e04..87460271d111d7 100644 --- a/packages/router/src/utils/config.ts +++ b/packages/router/src/utils/config.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {createEnvironmentInjector, EnvironmentInjector, Type, ɵisStandalone as isStandalone, ɵRuntimeError as RuntimeError} from '@angular/core'; +import {createEnvironmentInjector, EnvironmentInjector, isStandalone, Type, ɵRuntimeError as RuntimeError} from '@angular/core'; import {EmptyOutletComponent} from '../components/empty_outlet'; import {RuntimeErrorCode} from '../errors';