/**
* @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.dev/license
*/
import {Injector} from '../di/injector';
import {EnvironmentInjector, getNullInjector} from '../di/r3_injector';
import {Type} from '../interface/type';
import {ComponentRef} from '../linker/component_factory';
import {ComponentFactory} from './component_ref';
import {getComponentDef} from './def_getters';
import {Binding, DirectiveWithBindings} from './dynamic_bindings';
import {assertComponentDef} from './errors';
/**
* Creates a `ComponentRef` instance based on provided component type and a set of options.
*
* @usageNotes
*
* The example below demonstrates how the `createComponent` function can be used
* to create an instance of a ComponentRef dynamically and attach it to an ApplicationRef,
* so that it gets included into change detection cycles.
*
* Note: the example uses standalone components, but the function can also be used for
* non-standalone components (declared in an NgModule) as well.
*
* ```angular-ts
* @Component({
* standalone: true,
* template: `Hello {{ name }}!`
* })
* class HelloComponent {
* name = 'Angular';
* }
*
* @Component({
* standalone: true,
* template: `
`
* })
* class RootComponent {}
*
* // Bootstrap an application.
* const applicationRef = await bootstrapApplication(RootComponent);
*
* // Locate a DOM node that would be used as a host.
* const hostElement = document.getElementById('hello-component-host');
*
* // Get an `EnvironmentInjector` instance from the `ApplicationRef`.
* const environmentInjector = applicationRef.injector;
*
* // We can now create a `ComponentRef` instance.
* const componentRef = createComponent(HelloComponent, {hostElement, environmentInjector});
*
* // Last step is to register the newly created ref using the `ApplicationRef` instance
* // to include the component view into change detection cycles.
* applicationRef.attachView(componentRef.hostView);
* componentRef.changeDetectorRef.detectChanges();
* ```
*
* @param component Component class reference.
* @param options Set of options to use:
* * `environmentInjector`: An `EnvironmentInjector` instance to be used for the component.
* * `hostElement` (optional): A DOM node that should act as a host node for the component. If not
* provided, Angular creates one based on the tag name used in the component selector (and falls
* back to using `div` if selector doesn't have tag name info).
* * `elementInjector` (optional): An `ElementInjector` instance, see additional info about it
* [here](guide/di/hierarchical-dependency-injection#elementinjector).
* * `projectableNodes` (optional): A list of DOM nodes that should be projected through
* [``](api/core/ng-content) of the new component instance, e.g.,
* `[[element1, element2]]`: projects `element1` and `element2` into the same ``.
* `[[element1, element2], [element3]]`: projects `element1` and `element2` into one ``,
* and `element3` into a separate ``.
* * `directives` (optional): Directives that should be applied to the component.
* * `binding` (optional): Bindings to apply to the root component.
* @returns ComponentRef instance that represents a given Component.
*
* @publicApi
*/
export function createComponent(
component: Type,
options: {
environmentInjector: EnvironmentInjector;
hostElement?: Element;
elementInjector?: Injector;
projectableNodes?: Node[][];
directives?: (Type | DirectiveWithBindings)[];
bindings?: Binding[];
},
): ComponentRef {
ngDevMode && assertComponentDef(component);
const componentDef = getComponentDef(component)!;
const elementInjector = options.elementInjector || getNullInjector();
const factory = new ComponentFactory(componentDef);
return factory.create(
elementInjector,
options.projectableNodes,
options.hostElement,
options.environmentInjector,
options.directives,
options.bindings,
);
}
/**
* An interface that describes the subset of component metadata
* that can be retrieved using the `reflectComponentType` function.
*
* @publicApi
*/
export interface ComponentMirror {
/**
* The component's HTML selector.
*/
get selector(): string;
/**
* The type of component the factory will create.
*/
get type(): Type;
/**
* The inputs of the component.
*/
get inputs(): ReadonlyArray<{
readonly propName: string;
readonly templateName: string;
readonly transform?: (value: any) => any;
readonly isSignal: boolean;
}>;
/**
* The outputs of the component.
*/
get outputs(): ReadonlyArray<{readonly propName: string; readonly templateName: string}>;
/**
* Selector for all elements in the component.
*/
get ngContentSelectors(): ReadonlyArray;
/**
* Whether this component is marked as standalone.
* Note: an extra flag, not present in `ComponentFactory`.
*/
get isStandalone(): boolean;
/**
* // TODO(signals): Remove internal and add public documentation
* @internal
*/
get isSignal(): 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.
*
* ```angular-ts
* @Component({
* standalone: true,
* selector: 'foo-component',
* template: `
*
*
* `,
* })
* class FooComponent {
* @Input('inputName') inputPropName: string;
* @Output('outputName') outputPropName = new EventEmitter();
* }
*
* 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 `` in a template, the selector defaults to `*`
* 'content-selector-a' // second `` in a template
* ]);
* ```
*
* @param component Component class reference.
* @returns An object that allows to retrieve component metadata.
*
* @publicApi
*/
export function reflectComponentType(component: Type): ComponentMirror | null {
const componentDef = getComponentDef(component);
if (!componentDef) return null;
const factory = new ComponentFactory(componentDef);
return {
get selector(): string {
return factory.selector;
},
get type(): Type {
return factory.componentType;
},
get inputs(): ReadonlyArray<{
propName: string;
templateName: string;
transform?: (value: any) => any;
isSignal: boolean;
}> {
return factory.inputs;
},
get outputs(): ReadonlyArray<{propName: string; templateName: string}> {
return factory.outputs;
},
get ngContentSelectors(): ReadonlyArray {
return factory.ngContentSelectors;
},
get isStandalone(): boolean {
return componentDef.standalone;
},
get isSignal(): boolean {
return componentDef.signals;
},
};
}