mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
feat(core): add support for Types in ViewContainerRef.createComponent (#43022)
With Ivy it's possible to create a `ComponentFactory` instance based on the generated Component def, thus the `ViewContainerRef.createComponent` can accept a Component instance and create a factory inside, rather than requiring additional complexity of creating a ComponentFactory outside (by invoking `ComponentFactoryResolver`). This should simplify the API that is used for creating components dynamically. DEPRECATION: Angular no longer requires component factories to dynamically create components. The factory-based signature of the `ViewContainerRef.createComponent` function is deprecated in favor of a different signature that allows passing component classes instead. PR Close #43022
This commit is contained in:
parent
ab3de40ba3
commit
7dccbdd27b
13 changed files with 355 additions and 36 deletions
|
|
@ -54,6 +54,7 @@ v12 - v15
|
|||
| `@angular/core` | [`defineInjectable`](#core) | <!--v8--> v11 |
|
||||
| `@angular/core` | [`entryComponents`](api/core/NgModule#entryComponents) | <!--v9--> v11 |
|
||||
| `@angular/core` | [`ANALYZE_FOR_ENTRY_COMPONENTS`](api/core/ANALYZE_FOR_ENTRY_COMPONENTS) | <!--v9--> v11 |
|
||||
| `@angular/core` | [Factory-based signature of `ViewContainerRef.createComponent`](api/core/ViewContainerRef#createComponent) | <!--v13--> v15 |
|
||||
| `@angular/router` | [`loadChildren` string syntax](#loadChildren) | <!--v9--> v11 |
|
||||
| `@angular/core/testing` | [`TestBed.get`](#testing) | <!--v9--> v12 |
|
||||
| `@angular/core/testing` | [`async`](#testing) | <!--v9--> v12 |
|
||||
|
|
@ -108,6 +109,7 @@ This section contains a complete list all of the currently-deprecated APIs, with
|
|||
| `ViewChildren.emitDistinctChangesOnly` / `ContentChildren.emitDistinctChangesOnly` | none (was part of [issue #40091](https://github.com/angular/angular/issues/40091)) | | This is a temporary flag introduced as part of bugfix of [issue #40091](https://github.com/angular/angular/issues/40091) and will be removed. |
|
||||
| Factory-based signature of [`ApplicationRef.bootstrap`](api/core/ApplicationRef#bootstrap) | Type-based signature of [`ApplicationRef.bootstrap`](api/core/ApplicationRef#bootstrap) | v13 | With Ivy, there is no need to resolve Component factory and Component Type can be provided directly. |
|
||||
| [`PlatformRef.bootstrapModuleFactory`](api/core/PlatformRef#bootstrapModuleFactory) | [`PlatformRef.bootstrapModule`](api/core/PlatformRef#bootstrapModule) | v13 | With Ivy, there is no need to resolve NgModule factory and NgModule Type can be provided directly. |
|
||||
| [Factory-based signature of `ViewContainerRef.createComponent`](api/core/ViewContainerRef#createComponent) | [Type-based signature of `ViewContainerRef.createComponent`](api/core/ViewContainerRef#createComponent) | v13 | Angular no longer requires component factories to dynamically create components. Use different signature of the `createComponent` method, which allows passing Component class directly. |
|
||||
|
||||
{@a testing}
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,23 @@
|
|||
"packages/core/src/linker/template_ref.ts",
|
||||
"packages/core/src/render3/instructions/shared.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/error_handler.ts",
|
||||
"packages/core/src/errors.ts",
|
||||
"packages/core/src/view/types.ts",
|
||||
"packages/core/src/linker/view_container_ref.ts",
|
||||
"packages/core/src/render3/component_ref.ts",
|
||||
"packages/core/src/render3/component.ts",
|
||||
"packages/core/src/render3/instructions/shared.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/error_handler.ts",
|
||||
"packages/core/src/errors.ts",
|
||||
"packages/core/src/view/types.ts",
|
||||
"packages/core/src/linker/view_container_ref.ts",
|
||||
"packages/core/src/render3/component_ref.ts",
|
||||
"packages/core/src/render3/instructions/shared.ts"
|
||||
],
|
||||
[
|
||||
"packages/core/src/error_handler.ts",
|
||||
"packages/core/src/errors.ts",
|
||||
|
|
|
|||
|
|
@ -1358,7 +1358,14 @@ export interface ViewChildrenDecorator {
|
|||
// @public
|
||||
export abstract class ViewContainerRef {
|
||||
abstract clear(): void;
|
||||
abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;
|
||||
abstract createComponent<C>(componentType: Type<C>, options?: {
|
||||
index?: number;
|
||||
injector?: Injector;
|
||||
ngModuleRef?: NgModuleRef<unknown>;
|
||||
projectableNodes?: Node[][];
|
||||
}): ComponentRef<C>;
|
||||
// @deprecated
|
||||
abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModuleRef?: NgModuleRef<any>): ComponentRef<C>;
|
||||
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>;
|
||||
abstract detach(index?: number): ViewRef | null;
|
||||
abstract get element(): ElementRef;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2017": 4841,
|
||||
"main-es2017": 462493,
|
||||
"main-es2017": 463023,
|
||||
"polyfills-es2017": 55373,
|
||||
"styles": 69772,
|
||||
"light-theme": 79025,
|
||||
|
|
|
|||
|
|
@ -33,4 +33,4 @@ export {global as ɵglobal} from './util/global';
|
|||
export {isObservable as ɵisObservable, isPromise as ɵisPromise, isSubscribable as ɵisSubscribable} from './util/lang';
|
||||
export {stringify as ɵstringify} from './util/stringify';
|
||||
export {clearOverrides as ɵclearOverrides, initServicesIfNeeded as ɵinitServicesIfNeeded, overrideComponentView as ɵoverrideComponentView, overrideProvider as ɵoverrideProvider} from './view/index';
|
||||
export {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from './view/provider';
|
||||
export {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from './view/provider_flags';
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@
|
|||
*/
|
||||
|
||||
import {Injector} from '../di/injector';
|
||||
import {isType, Type} from '../interface/type';
|
||||
import {assertNodeInjector} from '../render3/assert';
|
||||
import {ComponentFactory as R3ComponentFactory} from '../render3/component_ref';
|
||||
import {getComponentDef} from '../render3/definition';
|
||||
import {getParentInjectorLocation, NodeInjector} from '../render3/di';
|
||||
import {addToViewTree, createLContainer} from '../render3/instructions/shared';
|
||||
import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE, VIEW_REFS} from '../render3/interfaces/container';
|
||||
|
|
@ -23,8 +26,9 @@ import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from '
|
|||
import {getNativeByTNode, unwrapRNode, viewAttachedToContainer} from '../render3/util/view_utils';
|
||||
import {ViewRef as R3ViewRef} from '../render3/view_ref';
|
||||
import {addToArray, removeFromArray} from '../util/array_utils';
|
||||
import {assertEqual, assertGreaterThan, assertLessThan} from '../util/assert';
|
||||
import {assertDefined, assertEqual, assertGreaterThan, assertLessThan} from '../util/assert';
|
||||
import {noop} from '../util/noop';
|
||||
|
||||
import {ComponentFactory, ComponentRef} from './component_factory';
|
||||
import {createElementRef, ElementRef} from './element_ref';
|
||||
import {NgModuleRef} from './ng_module_factory';
|
||||
|
|
@ -111,19 +115,47 @@ export abstract class ViewContainerRef {
|
|||
/**
|
||||
* Instantiates a single component and inserts its host view into this container.
|
||||
*
|
||||
* @param componentFactory The factory to use.
|
||||
* @param componentType Component Type to use.
|
||||
* @param options An object that contains extra parameters:
|
||||
* * index: the index at which to insert the new component's host view into this container.
|
||||
* If not specified, appends the new view as the last entry.
|
||||
* * injector: the injector to use as the parent for the new component.
|
||||
* * ngModuleRef: an NgModuleRef of the component's NgModule, you should almost always provide
|
||||
* this to ensure that all expected providers are available for the component
|
||||
* instantiation.
|
||||
* * projectableNodes: list of DOM nodes that should be projected through
|
||||
* [`<ng-content>`](api/core/ng-content) of the new component instance.
|
||||
*
|
||||
* @returns The new `ComponentRef` which contains the component instance and the host view.
|
||||
*/
|
||||
abstract createComponent<C>(componentType: Type<C>, options?: {
|
||||
index?: number,
|
||||
injector?: Injector,
|
||||
ngModuleRef?: NgModuleRef<unknown>,
|
||||
projectableNodes?: Node[][],
|
||||
}): ComponentRef<C>;
|
||||
|
||||
/**
|
||||
* Instantiates a single component and inserts its host view into this container.
|
||||
*
|
||||
* @param componentFactory Component factory to use.
|
||||
* @param index The index at which to insert the new component's host view into this container.
|
||||
* If not specified, appends the new view as the last entry.
|
||||
* @param injector The injector to use as the parent for the new component.
|
||||
* @param projectableNodes
|
||||
* @param ngModule
|
||||
* @param projectableNodes List of DOM nodes that should be projected through
|
||||
* [`<ng-content>`](api/core/ng-content) of the new component instance.
|
||||
* @param ngModuleRef An instance of the NgModuleRef that represent an NgModule.
|
||||
* This information is used to retrieve corresponding NgModule injector.
|
||||
*
|
||||
* @returns The new component instance, containing the host view.
|
||||
* @returns The new `ComponentRef` which contains the component instance and the host view.
|
||||
*
|
||||
* @deprecated Angular no longer requires component factories to dynamically create components.
|
||||
* Use different signature of the `createComponent` method, which allows passing
|
||||
* Component class directly.
|
||||
*/
|
||||
abstract createComponent<C>(
|
||||
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
|
||||
projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;
|
||||
projectableNodes?: any[][], ngModuleRef?: NgModuleRef<any>): ComponentRef<C>;
|
||||
|
||||
/**
|
||||
* Inserts a view into this container.
|
||||
|
|
@ -239,10 +271,77 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
|
|||
return viewRef;
|
||||
}
|
||||
|
||||
override createComponent<C>(componentType: Type<C>, options?: {
|
||||
index?: number,
|
||||
injector?: Injector,
|
||||
projectableNodes?: Node[][],
|
||||
ngModuleRef?: NgModuleRef<unknown>,
|
||||
}): ComponentRef<C>;
|
||||
/**
|
||||
* @deprecated Angular no longer requires component factories to dynamically create components.
|
||||
* Use different signature of the `createComponent` method, which allows passing
|
||||
* Component class directly.
|
||||
*/
|
||||
override createComponent<C>(
|
||||
componentFactory: ComponentFactory<C>, index?: number|undefined,
|
||||
injector?: Injector|undefined, projectableNodes?: any[][]|undefined,
|
||||
ngModuleRef?: NgModuleRef<any>|undefined): ComponentRef<C>;
|
||||
override createComponent<C>(
|
||||
componentFactoryOrType: ComponentFactory<C>|Type<C>, indexOrOptions?: number|undefined|{
|
||||
index?: number,
|
||||
injector?: Injector,
|
||||
ngModuleRef?: NgModuleRef<unknown>,
|
||||
projectableNodes?: Node[][],
|
||||
},
|
||||
injector?: Injector|undefined, projectableNodes?: any[][]|undefined,
|
||||
ngModuleRef?: NgModuleRef<any>|undefined): ComponentRef<C> {
|
||||
const isComponentFactory = componentFactoryOrType && !isType(componentFactoryOrType);
|
||||
let index: number|undefined;
|
||||
|
||||
// This function supports 2 signatures and we need to handle options correctly for both:
|
||||
// 1. When first argument is a Component type. This signature also requires extra
|
||||
// options to be provided as as object (more ergonomic option).
|
||||
// 2. First argument is a Component factory. In this case extra options are represented as
|
||||
// positional arguments. This signature is less ergonomic and will be deprecated.
|
||||
if (isComponentFactory) {
|
||||
if (ngDevMode) {
|
||||
assertEqual(
|
||||
typeof indexOrOptions !== 'object', true,
|
||||
'It looks like Component factory was provided as the first argument ' +
|
||||
'and an options object as the second argument. This combination of arguments ' +
|
||||
'is incompatible. You can either change the first argument to provide Component ' +
|
||||
'type or change the second argument to be a number (representing an index at ' +
|
||||
'which to insert the new component\'s host view into this container)');
|
||||
}
|
||||
index = indexOrOptions as number | undefined;
|
||||
} else {
|
||||
if (ngDevMode) {
|
||||
assertDefined(
|
||||
getComponentDef(componentFactoryOrType),
|
||||
`Provided Component class doesn't contain Component definition. ` +
|
||||
`Please check whether provided class has @Component decorator.`);
|
||||
assertEqual(
|
||||
typeof indexOrOptions !== 'number', true,
|
||||
'It looks like Component type was provided as the first argument ' +
|
||||
'and a number (representing an index at which to insert the new component\'s ' +
|
||||
'host view into this container as the second argument. This combination of arguments ' +
|
||||
'is incompatible. Please use an object as the second argument instead.');
|
||||
}
|
||||
const options = (indexOrOptions || {}) as {
|
||||
index?: number,
|
||||
injector?: Injector,
|
||||
ngModuleRef?: NgModuleRef<unknown>,
|
||||
projectableNodes?: Node[][],
|
||||
};
|
||||
index = options.index;
|
||||
injector = options.injector;
|
||||
projectableNodes = options.projectableNodes;
|
||||
ngModuleRef = options.ngModuleRef;
|
||||
}
|
||||
|
||||
const componentFactory: ComponentFactory<C> = isComponentFactory ?
|
||||
componentFactoryOrType as ComponentFactory<C>:
|
||||
new R3ComponentFactory(getComponentDef(componentFactoryOrType)!);
|
||||
const contextInjector = injector || this.parentInjector;
|
||||
if (!ngModuleRef && (componentFactory as any).ngModule == null && contextInjector) {
|
||||
// DO NOT REFACTOR. The code here used to have a `value || undefined` expression
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory
|
|||
import {RendererFactory2} from '../render/api';
|
||||
import {Sanitizer} from '../sanitization/sanitizer';
|
||||
import {VERSION} from '../version';
|
||||
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '../view/provider';
|
||||
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '../view/provider_flags';
|
||||
import {assertComponentType} from './assert';
|
||||
import {createRootComponent, createRootComponentView, createRootContext, LifecycleHooksFeature} from './component';
|
||||
import {getComponentDef} from './definition';
|
||||
|
|
|
|||
|
|
@ -7,10 +7,7 @@
|
|||
*/
|
||||
|
||||
|
||||
import {ChangeDetectorRef, injectChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||
import {InjectFlags} from '../di/interface/injector';
|
||||
import {createTemplateRef, TemplateRef} from '../linker/template_ref';
|
||||
import {throwProviderNotFoundError} from './errors_di';
|
||||
import {TNode} from './interfaces/node';
|
||||
import {LView} from './interfaces/view';
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {Renderer2} from '../render/api';
|
|||
import {isObservable} from '../util/lang';
|
||||
import {stringify} from '../util/stringify';
|
||||
|
||||
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from './provider_flags';
|
||||
import {createChangeDetectorRef, createInjector} from './refs';
|
||||
import {asElementData, asProviderData, BindingDef, BindingFlags, DepDef, DepFlags, NodeDef, NodeFlags, OutputDef, OutputType, ProviderData, QueryValueType, Services, shouldCallLifecycleInitHook, ViewData, ViewFlags, ViewState} from './types';
|
||||
import {calcBindingFlags, checkBinding, dispatchEvent, isComponentView, splitDepsDsl, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util';
|
||||
|
|
@ -318,25 +319,6 @@ function callFactory(
|
|||
}
|
||||
}
|
||||
|
||||
// This default value is when checking the hierarchy for a token.
|
||||
//
|
||||
// It means both:
|
||||
// - the token is not provided by the current injector,
|
||||
// - only the element injectors should be checked (ie do not check module injectors
|
||||
//
|
||||
// mod1
|
||||
// /
|
||||
// el1 mod2
|
||||
// \ /
|
||||
// el2
|
||||
//
|
||||
// When requesting el2.injector.get(token), we should check in the following order and return the
|
||||
// first found value:
|
||||
// - el2.injector.get(token, default)
|
||||
// - el1.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) -> do not check the module
|
||||
// - mod2.injector.get(token, default)
|
||||
export const NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR = {};
|
||||
|
||||
export function resolveDep(
|
||||
view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef,
|
||||
notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
|
||||
|
|
|
|||
26
packages/core/src/view/provider_flags.ts
Normal file
26
packages/core/src/view/provider_flags.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
// This default value is when checking the hierarchy for a token.
|
||||
//
|
||||
// It means both:
|
||||
// - the token is not provided by the current injector,
|
||||
// - only the element injectors should be checked (ie do not check module injectors
|
||||
//
|
||||
// mod1
|
||||
// /
|
||||
// el1 mod2
|
||||
// \ /
|
||||
// el2
|
||||
//
|
||||
// When requesting el2.injector.get(token), we should check in the following order and return the
|
||||
// first found value:
|
||||
// - el2.injector.get(token, default)
|
||||
// - el1.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) -> do not check the module
|
||||
// - mod2.injector.get(token, default)
|
||||
export const NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR = {};
|
||||
|
|
@ -17,6 +17,7 @@ import {InternalNgModuleRef, NgModuleRef} from '../linker/ng_module_factory';
|
|||
import {TemplateRef} from '../linker/template_ref';
|
||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||
import {EmbeddedViewRef, InternalViewRef, ViewRef, ViewRefTracker} from '../linker/view_ref';
|
||||
import {assertEqual} from '../util/assert';
|
||||
import {stringify} from '../util/stringify';
|
||||
import {VERSION} from '../version';
|
||||
|
||||
|
|
@ -191,9 +192,20 @@ class ViewContainerRef_ implements ViewContainerData {
|
|||
}
|
||||
|
||||
createComponent<C>(
|
||||
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
|
||||
projectableNodes?: any[][], ngModuleRef?: NgModuleRef<any>): ComponentRef<C> {
|
||||
componentFactoryOrType: ComponentFactory<C>|Type<C>, indexOrOptions?: number|{},
|
||||
injector?: Injector, projectableNodes?: any[][],
|
||||
ngModuleRef?: NgModuleRef<any>): ComponentRef<C> {
|
||||
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
||||
assertEqual(
|
||||
typeof componentFactoryOrType !== 'function', true,
|
||||
'ViewEngine does not support Type as an argument for \'componentFactoryOrType\'');
|
||||
assertEqual(
|
||||
typeof indexOrOptions !== 'object', true,
|
||||
'ViewEngine does not support options as an object provided via second argument');
|
||||
}
|
||||
const index = indexOrOptions as number | undefined;
|
||||
const contextInjector = injector || this.parentInjector;
|
||||
const componentFactory = componentFactoryOrType as ComponentFactory<C>;
|
||||
if (!ngModuleRef && !(componentFactory instanceof ComponentFactoryBoundToModule)) {
|
||||
ngModuleRef = contextInjector.get(NgModuleRef);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@
|
|||
|
||||
import {CommonModule, DOCUMENT} from '@angular/common';
|
||||
import {computeMsgId} from '@angular/compiler';
|
||||
import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, Injector, NgModule, NO_ERRORS_SCHEMA, OnDestroy, OnInit, Pipe, PipeTransform, QueryList, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵsetDocument} from '@angular/core';
|
||||
import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, InjectionToken, Injector, NgModule, NgModuleRef, NO_ERRORS_SCHEMA, OnDestroy, OnInit, Pipe, PipeTransform, QueryList, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵsetDocument} from '@angular/core';
|
||||
import {Input} from '@angular/core/src/metadata';
|
||||
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
|
||||
import {TestBed, TestComponentRenderer} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed, TestComponentRenderer} from '@angular/core/testing';
|
||||
import {clearTranslations, loadTranslations} from '@angular/localize';
|
||||
import {By, DomSanitizer, ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
|
@ -1495,6 +1495,123 @@ describe('ViewContainerRef', () => {
|
|||
|
||||
expect(fixture.debugElement.nativeElement.innerHTML).toContain('Child Component');
|
||||
});
|
||||
|
||||
describe('createComponent using Type', () => {
|
||||
const TOKEN_A = new InjectionToken('A');
|
||||
const TOKEN_B = new InjectionToken('B');
|
||||
|
||||
@Component({
|
||||
selector: 'child-a',
|
||||
template: `[Child Component A]`,
|
||||
})
|
||||
class ChildA {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'child-b',
|
||||
template: `
|
||||
[Child Component B]
|
||||
<ng-content></ng-content>
|
||||
{{ tokenA }}
|
||||
{{ tokenB }}
|
||||
`,
|
||||
})
|
||||
class ChildB {
|
||||
constructor(private injector: Injector) {}
|
||||
get tokenA() {
|
||||
return this.injector.get(TOKEN_A);
|
||||
}
|
||||
get tokenB() {
|
||||
return this.injector.get(TOKEN_B);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: '',
|
||||
providers: [
|
||||
{provide: TOKEN_B, useValue: '[TokenValueB]'},
|
||||
]
|
||||
})
|
||||
class App {
|
||||
constructor(
|
||||
public viewContainerRef: ViewContainerRef, public ngModuleRef: NgModuleRef<unknown>,
|
||||
public injector: Injector) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [App, ChildA, ChildB],
|
||||
providers: [
|
||||
{provide: TOKEN_A, useValue: '[TokenValueA]'},
|
||||
]
|
||||
})
|
||||
class AppModule {
|
||||
}
|
||||
|
||||
let fixture!: ComponentFixture<App>;
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({imports: [AppModule]});
|
||||
fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be able to create a component when Type is provided', () => {
|
||||
if (ivyEnabled) {
|
||||
fixture.componentInstance.viewContainerRef.createComponent(ChildA);
|
||||
expect(fixture.nativeElement.parentNode.textContent).toContain('[Child Component A]');
|
||||
} else {
|
||||
expect(() => {
|
||||
fixture.componentInstance.viewContainerRef.createComponent(ChildA);
|
||||
}).toThrowError(/ViewEngine does not support Type/);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw if class without @Component decorator is used as Component type', () => {
|
||||
class MyClassWithoutComponentDecorator {}
|
||||
const createComponent = () => {
|
||||
fixture.componentInstance.viewContainerRef.createComponent(
|
||||
MyClassWithoutComponentDecorator);
|
||||
};
|
||||
if (ivyEnabled) {
|
||||
expect(createComponent)
|
||||
.toThrowError(/Provided Component class doesn't contain Component definition./);
|
||||
} else {
|
||||
expect(createComponent).toThrowError(/ViewEngine does not support Type/);
|
||||
}
|
||||
});
|
||||
|
||||
onlyInIvy('Ivy-only checks for the `createComponent` API with `options` argument')
|
||||
.describe('`options` argument handling', () => {
|
||||
it('should work correctly when an empty object is provided', () => {
|
||||
fixture.componentInstance.viewContainerRef.createComponent(ChildA, {});
|
||||
expect(fixture.nativeElement.parentNode.textContent).toContain('[Child Component A]');
|
||||
});
|
||||
|
||||
it('should take provided `options` arguments into account', () => {
|
||||
const {viewContainerRef, ngModuleRef, injector} = fixture.componentInstance;
|
||||
viewContainerRef.createComponent(ChildA);
|
||||
|
||||
const projectableNode = document.createElement('div');
|
||||
const textNode = document.createTextNode('[Projectable Node]');
|
||||
projectableNode.appendChild(textNode);
|
||||
const projectableNodes = [[projectableNode]];
|
||||
|
||||
// Insert ChildB in front of ChildA (since index = 0)
|
||||
viewContainerRef.createComponent(
|
||||
ChildB, {index: 0, injector, ngModuleRef, projectableNodes});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.parentNode.textContent.trim())
|
||||
.toContain(
|
||||
'[Child Component B] ' +
|
||||
'[Projectable Node] ' +
|
||||
'[TokenValueA] ' +
|
||||
'[TokenValueB] ' +
|
||||
'[Child Component A]');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertion points and declaration points', () => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@
|
|||
{
|
||||
"name": "ChangeDetectionStrategy"
|
||||
},
|
||||
{
|
||||
"name": "ComponentFactory"
|
||||
},
|
||||
{
|
||||
"name": "ComponentRef"
|
||||
},
|
||||
{
|
||||
"name": "DefaultIterableDiffer"
|
||||
},
|
||||
|
|
@ -32,6 +38,9 @@
|
|||
{
|
||||
"name": "IterableDiffers"
|
||||
},
|
||||
{
|
||||
"name": "LifecycleHooksFeature"
|
||||
},
|
||||
{
|
||||
"name": "NG_COMP_DEF"
|
||||
},
|
||||
|
|
@ -56,6 +65,9 @@
|
|||
{
|
||||
"name": "NOT_FOUND"
|
||||
},
|
||||
{
|
||||
"name": "NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR"
|
||||
},
|
||||
{
|
||||
"name": "NO_CHANGE"
|
||||
},
|
||||
|
|
@ -92,6 +104,12 @@
|
|||
{
|
||||
"name": "RecordViewTuple"
|
||||
},
|
||||
{
|
||||
"name": "RendererFactory2"
|
||||
},
|
||||
{
|
||||
"name": "RootViewRef"
|
||||
},
|
||||
{
|
||||
"name": "RuntimeError"
|
||||
},
|
||||
|
|
@ -104,6 +122,9 @@
|
|||
{
|
||||
"name": "SWITCH_VIEW_CONTAINER_REF_FACTORY"
|
||||
},
|
||||
{
|
||||
"name": "Sanitizer"
|
||||
},
|
||||
{
|
||||
"name": "SimpleChange"
|
||||
},
|
||||
|
|
@ -137,9 +158,15 @@
|
|||
{
|
||||
"name": "TodoStore"
|
||||
},
|
||||
{
|
||||
"name": "VERSION"
|
||||
},
|
||||
{
|
||||
"name": "VE_ViewContainerRef"
|
||||
},
|
||||
{
|
||||
"name": "Version"
|
||||
},
|
||||
{
|
||||
"name": "ViewContainerRef"
|
||||
},
|
||||
|
|
@ -260,6 +287,9 @@
|
|||
{
|
||||
"name": "createDirectivesInstances"
|
||||
},
|
||||
{
|
||||
"name": "createElementNode"
|
||||
},
|
||||
{
|
||||
"name": "createElementRef"
|
||||
},
|
||||
|
|
@ -275,6 +305,15 @@
|
|||
{
|
||||
"name": "createNodeInjector"
|
||||
},
|
||||
{
|
||||
"name": "createRootComponent"
|
||||
},
|
||||
{
|
||||
"name": "createRootComponentView"
|
||||
},
|
||||
{
|
||||
"name": "createRootContext"
|
||||
},
|
||||
{
|
||||
"name": "createTView"
|
||||
},
|
||||
|
|
@ -296,6 +335,9 @@
|
|||
{
|
||||
"name": "detachView"
|
||||
},
|
||||
{
|
||||
"name": "detectChangesInRootView"
|
||||
},
|
||||
{
|
||||
"name": "detectChangesInternal"
|
||||
},
|
||||
|
|
@ -443,6 +485,9 @@
|
|||
{
|
||||
"name": "getSymbolIterator"
|
||||
},
|
||||
{
|
||||
"name": "getTNode"
|
||||
},
|
||||
{
|
||||
"name": "getTStylingRangeNext"
|
||||
},
|
||||
|
|
@ -563,6 +608,9 @@
|
|||
{
|
||||
"name": "leaveViewLight"
|
||||
},
|
||||
{
|
||||
"name": "locateHostElement"
|
||||
},
|
||||
{
|
||||
"name": "lookupTokenUsingModuleInjector"
|
||||
},
|
||||
|
|
@ -581,6 +629,9 @@
|
|||
{
|
||||
"name": "markViewDirty"
|
||||
},
|
||||
{
|
||||
"name": "maybeWrapInNotSelector"
|
||||
},
|
||||
{
|
||||
"name": "mergeHostAttribute"
|
||||
},
|
||||
|
|
@ -725,12 +776,21 @@
|
|||
{
|
||||
"name": "stringify"
|
||||
},
|
||||
{
|
||||
"name": "stringifyCSSSelector"
|
||||
},
|
||||
{
|
||||
"name": "stringifyForError"
|
||||
},
|
||||
{
|
||||
"name": "throwProviderNotFoundError"
|
||||
},
|
||||
{
|
||||
"name": "tickRootContext"
|
||||
},
|
||||
{
|
||||
"name": "toRefArray"
|
||||
},
|
||||
{
|
||||
"name": "toTStylingRange"
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue