Skip to content

Commit

Permalink
feat(core): Make the isStandalone() function available in public API
Browse files Browse the repository at this point in the history
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
  • Loading branch information
JeanMeche committed Nov 21, 2022
1 parent 414b1b2 commit 24d63cd
Show file tree
Hide file tree
Showing 17 changed files with 157 additions and 6 deletions.
2 changes: 2 additions & 0 deletions goldens/public-api/core/errors.md
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions goldens/public-api/core/index.md
Expand Up @@ -787,6 +787,9 @@ export interface InputDecorator {
// @public
export function isDevMode(): boolean;

// @public
export function isStandalone<T>(type: Type<T>): boolean;

// @public
export interface IterableChangeRecord<V> {
readonly currentIndex: number | null;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/core.ts
Expand Up @@ -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) {
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/core_render3_private_export.ts
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/errors.ts
Expand Up @@ -83,6 +83,7 @@ export const enum RuntimeErrorCode {
MISSING_ZONEJS = 908,
UNEXPECTED_ZONE_STATE = 909,
UNSAFE_IFRAME_ATTRS = -910,
UNEXPECTED_CLASS_TYPE = 911,
}

/**
Expand Down
25 changes: 23 additions & 2 deletions packages/core/src/render3/definition.ts
Expand Up @@ -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';
Expand All @@ -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. */
Expand Down Expand Up @@ -741,9 +742,29 @@ export function getPipeDef<T>(type: any): PipeDef<T>|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<T>(type: Type<T>): 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<T>(type: any, throwNotFound: true): NgModuleDef<T>;
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/render3/jit/module.ts
Expand Up @@ -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);
Expand Down
72 changes: 71 additions & 1 deletion packages/core/test/acceptance/standalone_spec.ts
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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();
});
});
});
Expand Up @@ -842,6 +842,9 @@
{
"name": "getNextLContainer"
},
{
"name": "getNgModuleDef"
},
{
"name": "getNodeInjectable"
},
Expand Down Expand Up @@ -1310,6 +1313,9 @@
{
"name": "stringifyCSSSelector"
},
{
"name": "stringifyForError"
},
{
"name": "style"
},
Expand Down
Expand Up @@ -620,6 +620,9 @@
{
"name": "getNextLContainer"
},
{
"name": "getNgModuleDef"
},
{
"name": "getNodeInjectable"
},
Expand Down Expand Up @@ -989,6 +992,9 @@
{
"name": "stringifyCSSSelector"
},
{
"name": "stringifyForError"
},
{
"name": "subscribeTo"
},
Expand Down
Expand Up @@ -893,6 +893,9 @@
{
"name": "getNextLContainer"
},
{
"name": "getNgModuleDef"
},
{
"name": "getNodeInjectable"
},
Expand Down Expand Up @@ -1466,6 +1469,9 @@
{
"name": "stringifyCSSSelector"
},
{
"name": "stringifyForError"
},
{
"name": "subscribeTo"
},
Expand Down
Expand Up @@ -854,6 +854,9 @@
{
"name": "getNextLContainer"
},
{
"name": "getNgModuleDef"
},
{
"name": "getNodeInjectable"
},
Expand Down Expand Up @@ -1442,6 +1445,9 @@
{
"name": "stringifyCSSSelector"
},
{
"name": "stringifyForError"
},
{
"name": "subscribeTo"
},
Expand Down
12 changes: 12 additions & 0 deletions packages/core/test/bundling/hello_world/bundle.golden_symbols.json
Expand Up @@ -113,6 +113,9 @@
{
"name": "NG_COMP_DEF"
},
{
"name": "NG_DIR_DEF"
},
{
"name": "NG_ELEMENT_ID"
},
Expand All @@ -131,6 +134,9 @@
{
"name": "NG_MOD_DEF"
},
{
"name": "NG_PIPE_DEF"
},
{
"name": "NG_PROV_DEF"
},
Expand Down Expand Up @@ -458,6 +464,9 @@
{
"name": "getNextLContainer"
},
{
"name": "getNgModuleDef"
},
{
"name": "getNodeInjectable"
},
Expand Down Expand Up @@ -749,6 +758,9 @@
{
"name": "stringifyCSSSelector"
},
{
"name": "stringifyForError"
},
{
"name": "subscribeTo"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/core/test/bundling/router/bundle.golden_symbols.json
Expand Up @@ -1763,6 +1763,9 @@
{
"name": "stringifyCSSSelector"
},
{
"name": "stringifyForError"
},
{
"name": "stripTrailingSlash"
},
Expand Down
Expand Up @@ -176,6 +176,9 @@
{
"name": "NG_INJ_DEF"
},
{
"name": "NG_MOD_DEF"
},
{
"name": "NG_PIPE_DEF"
},
Expand Down Expand Up @@ -842,6 +845,9 @@
{
"name": "stringifyCSSSelector"
},
{
"name": "stringifyForError"
},
{
"name": "subscribeTo"
},
Expand Down
6 changes: 6 additions & 0 deletions packages/core/test/bundling/todo/bundle.golden_symbols.json
Expand Up @@ -749,6 +749,9 @@
{
"name": "getNextLContainer"
},
{
"name": "getNgModuleDef"
},
{
"name": "getNodeInjectable"
},
Expand Down Expand Up @@ -1223,6 +1226,9 @@
{
"name": "stringifyCSSSelector"
},
{
"name": "stringifyForError"
},
{
"name": "subscribeTo"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/router/src/utils/config.ts
Expand Up @@ -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';
Expand Down

0 comments on commit 24d63cd

Please sign in to comment.