mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(core): add reflectComponentType function (#46685)
This commit introduces a new function that allows creating a object which exposes a number of getters to retrieve information about a given component. Closes #44926. PR Close #46685
This commit is contained in:
committed by
Jessica Janiuk
parent
d1e83e1b30
commit
10becab70e
@@ -37,7 +37,7 @@ export * from './core_render3_private_export';
|
||||
export {SecurityContext} from './sanitization/security';
|
||||
export {Sanitizer} from './sanitization/sanitizer';
|
||||
export {createNgModule, createNgModuleRef, createEnvironmentInjector} from './render3/ng_module_ref';
|
||||
export {createComponent} from './render3/component_ref';
|
||||
export {createComponent, reflectComponentType, ComponentMirror} from './render3/component_ref';
|
||||
|
||||
import {global} from './util/global';
|
||||
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
||||
|
||||
@@ -28,7 +28,7 @@ import {getComponentDef} from './definition';
|
||||
import {NodeInjector} from './di';
|
||||
import {assertComponentDef} from './errors';
|
||||
import {reportUnknownPropertyError} from './instructions/element_validation';
|
||||
import {createLView, createTView, initializeInputAndOutputAliases, locateHostElement, markDirtyIfOnPush, renderView, setInputsForProperty} from './instructions/shared';
|
||||
import {createLView, createTView, locateHostElement, markDirtyIfOnPush, renderView, setInputsForProperty} from './instructions/shared';
|
||||
import {ComponentDef} from './interfaces/definition';
|
||||
import {PropertyAliasValue, TContainerNode, TElementContainerNode, TElementNode, TNode} from './interfaces/node';
|
||||
import {RNode} from './interfaces/renderer_dom';
|
||||
@@ -359,7 +359,9 @@ export class ComponentRef<T> extends AbstractComponentRef<T> {
|
||||
* https://angular.io/guide/hierarchical-dependency-injection#elementinjector.
|
||||
* * `projectableNodes` (optional): A list of DOM nodes that should be projected through
|
||||
* [`<ng-content>`](api/core/ng-content) of the new component instance.
|
||||
* @returns
|
||||
* @returns ComponentRef instance that represents a given Component.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function createComponent<C>(component: Type<C>, options: {
|
||||
hostElement?: Element, environmentInjector: EnvironmentInjector,
|
||||
@@ -373,3 +375,103 @@ export function createComponent<C>(component: Type<C>, options: {
|
||||
return factory.create(
|
||||
elementInjector, options.projectableNodes, options.hostElement, options.environmentInjector);
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface that describes the subset of component metadata
|
||||
* that can be retrieved using the `reflectComponentType` function.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface ComponentMirror<C> {
|
||||
/**
|
||||
* The component's HTML selector.
|
||||
*/
|
||||
get selector(): string;
|
||||
/**
|
||||
* The type of component the factory will create.
|
||||
*/
|
||||
get type(): Type<C>;
|
||||
/**
|
||||
* The inputs of the component.
|
||||
*/
|
||||
get inputs(): ReadonlyArray<{propName: string, templateName: string}>;
|
||||
/**
|
||||
* The outputs of the component.
|
||||
*/
|
||||
get outputs(): ReadonlyArray<{propName: string, templateName: string}>;
|
||||
/**
|
||||
* Selector for all <ng-content> elements in the component.
|
||||
*/
|
||||
get ngContentSelectors(): ReadonlyArray<string>;
|
||||
/**
|
||||
* Whether this component is marked as standalone.
|
||||
* Note: an extra flag, not present in `ComponentFactory`.
|
||||
*/
|
||||
get isStandalone(): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an object that allows to retrieve component metadata.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* The example below demonstrates how to use the function and how the fields
|
||||
* of the returned object map to the component metadata.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* standalone: true,
|
||||
* selector: 'foo-component',
|
||||
* template: `
|
||||
* <ng-content></ng-content>
|
||||
* <ng-content select="content-selector-a"></ng-content>
|
||||
* `,
|
||||
* })
|
||||
* class FooComponent {
|
||||
* @Input('inputName') inputPropName: string;
|
||||
* @Output('outputName') outputPropName = new EventEmitter<void>();
|
||||
* }
|
||||
*
|
||||
* const mirror = reflectComponentType(FooComponent)!;
|
||||
* expect(mirror.type).toBe(FooComponent);
|
||||
* expect(mirror.selector).toBe('foo-component');
|
||||
* expect(mirror.isStandalone).toBe(true);
|
||||
* expect(mirror.inputs).toEqual([{propName: 'inputName', templateName: 'inputPropName'}]);
|
||||
* expect(mirror.outputs).toEqual([{propName: 'outputName', templateName: 'outputPropName'}]);
|
||||
* expect(mirror.ngContentSelectors).toEqual([
|
||||
* '*', // first `<ng-content>` in a template, the selector defaults to `*`
|
||||
* 'content-selector-a' // second `<ng-content>` in a template
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param component Component class reference.
|
||||
* @returns An object that allows to retrieve component metadata.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function reflectComponentType<C>(component: Type<C>): ComponentMirror<C>|null {
|
||||
const componentDef = getComponentDef(component);
|
||||
if (!componentDef) return null;
|
||||
|
||||
const factory = new ComponentFactory<C>(componentDef);
|
||||
return {
|
||||
get selector(): string {
|
||||
return factory.selector;
|
||||
},
|
||||
get type(): Type<C> {
|
||||
return factory.componentType;
|
||||
},
|
||||
get inputs(): ReadonlyArray<{propName: string, templateName: string}> {
|
||||
return factory.inputs;
|
||||
},
|
||||
get outputs(): ReadonlyArray<{propName: string, templateName: string}> {
|
||||
return factory.outputs;
|
||||
},
|
||||
get ngContentSelectors(): ReadonlyArray<string> {
|
||||
return factory.ngContentSelectors;
|
||||
},
|
||||
get isStandalone(): boolean {
|
||||
return componentDef.standalone;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {ApplicationRef, Component, ComponentFactoryResolver, ComponentRef, createComponent, createEnvironmentInjector, Directive, ElementRef, EmbeddedViewRef, EnvironmentInjector, inject, Injectable, InjectionToken, Injector, Input, NgModule, OnDestroy, Renderer2, Type, ViewChild, ViewContainerRef, ViewEncapsulation, ɵsetDocument} from '@angular/core';
|
||||
import {ApplicationRef, Component, ComponentFactoryResolver, ComponentRef, createComponent, createEnvironmentInjector, Directive, ElementRef, EmbeddedViewRef, EnvironmentInjector, inject, Injectable, InjectionToken, Injector, Input, NgModule, OnDestroy, reflectComponentType, Renderer2, Type, ViewChild, ViewContainerRef, ViewEncapsulation, ɵsetDocument} from '@angular/core';
|
||||
import {stringifyForError} from '@angular/core/src/render3/util/stringify_utils';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
@@ -866,4 +866,91 @@ describe('component', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('reflectComponentType', () => {
|
||||
it('should create an ComponentMirror for a standalone component', () => {
|
||||
@Component({
|
||||
selector: 'standalone-component',
|
||||
standalone: true,
|
||||
template: `
|
||||
<ng-content></ng-content>
|
||||
<ng-content select="content-selector-a"></ng-content>
|
||||
<ng-content select="content-selector-b"></ng-content>
|
||||
<ng-content></ng-content>
|
||||
`,
|
||||
inputs: ['input-a', 'input-b:input-alias-b'],
|
||||
outputs: ['output-a', 'output-b:output-alias-b'],
|
||||
})
|
||||
class StandaloneComponent {
|
||||
}
|
||||
|
||||
const mirror = reflectComponentType(StandaloneComponent)!;
|
||||
|
||||
expect(mirror.selector).toBe('standalone-component');
|
||||
expect(mirror.type).toBe(StandaloneComponent);
|
||||
expect(mirror.isStandalone).toEqual(true);
|
||||
expect(mirror.inputs).toEqual([
|
||||
{propName: 'input-a', templateName: 'input-a'},
|
||||
{propName: 'input-b', templateName: 'input-alias-b'}
|
||||
]);
|
||||
expect(mirror.outputs).toEqual([
|
||||
{propName: 'output-a', templateName: 'output-a'},
|
||||
{propName: 'output-b', templateName: 'output-alias-b'}
|
||||
]);
|
||||
expect(mirror.ngContentSelectors).toEqual([
|
||||
'*', 'content-selector-a', 'content-selector-b', '*'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create an ComponentMirror for a non-standalone component', () => {
|
||||
@Component({
|
||||
selector: 'non-standalone-component',
|
||||
template: `
|
||||
<ng-content></ng-content>
|
||||
<ng-content select="content-selector-a"></ng-content>
|
||||
<ng-content select="content-selector-b"></ng-content>
|
||||
<ng-content></ng-content>
|
||||
`,
|
||||
inputs: ['input-a', 'input-b:input-alias-b'],
|
||||
outputs: ['output-a', 'output-b:output-alias-b'],
|
||||
})
|
||||
class NonStandaloneComponent {
|
||||
}
|
||||
|
||||
const mirror = reflectComponentType(NonStandaloneComponent)!;
|
||||
|
||||
expect(mirror.selector).toBe('non-standalone-component');
|
||||
expect(mirror.type).toBe(NonStandaloneComponent);
|
||||
expect(mirror.isStandalone).toEqual(false);
|
||||
expect(mirror.inputs).toEqual([
|
||||
{propName: 'input-a', templateName: 'input-a'},
|
||||
{propName: 'input-b', templateName: 'input-alias-b'}
|
||||
]);
|
||||
expect(mirror.outputs).toEqual([
|
||||
{propName: 'output-a', templateName: 'output-a'},
|
||||
{propName: 'output-b', templateName: 'output-alias-b'}
|
||||
]);
|
||||
expect(mirror.ngContentSelectors).toEqual([
|
||||
'*', 'content-selector-a', 'content-selector-b', '*'
|
||||
]);
|
||||
});
|
||||
|
||||
describe('error checking', () => {
|
||||
it('should throw when provided class is not a component', () => {
|
||||
class NotAnnotated {}
|
||||
|
||||
@Directive()
|
||||
class ADirective {
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
class AnInjectiable {
|
||||
}
|
||||
|
||||
expect(reflectComponentType(NotAnnotated)).toBe(null);
|
||||
expect(reflectComponentType(ADirective)).toBe(null);
|
||||
expect(reflectComponentType(AnInjectiable)).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user