From 7dccbdd27be13eb7287f535f482b1de2c13fca74 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Wed, 5 May 2021 16:55:44 -0700 Subject: [PATCH] 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 --- aio/content/guide/deprecations.md | 2 + goldens/circular-deps/packages.json | 17 +++ goldens/public-api/core/core.md | 9 +- goldens/size-tracking/aio-payloads.json | 2 +- packages/core/src/core_private_export.ts | 2 +- .../core/src/linker/view_container_ref.ts | 111 +++++++++++++++- packages/core/src/render3/component_ref.ts | 2 +- .../view_engine_compatibility_prebound.ts | 3 - packages/core/src/view/provider.ts | 20 +-- packages/core/src/view/provider_flags.ts | 26 ++++ packages/core/src/view/refs.ts | 16 ++- .../acceptance/view_container_ref_spec.ts | 121 +++++++++++++++++- .../bundling/todo/bundle.golden_symbols.json | 60 +++++++++ 13 files changed, 355 insertions(+), 36 deletions(-) create mode 100644 packages/core/src/view/provider_flags.ts diff --git a/aio/content/guide/deprecations.md b/aio/content/guide/deprecations.md index b5198ca3ce4..f5a153d7556 100644 --- a/aio/content/guide/deprecations.md +++ b/aio/content/guide/deprecations.md @@ -54,6 +54,7 @@ v12 - v15 | `@angular/core` | [`defineInjectable`](#core) | v11 | | `@angular/core` | [`entryComponents`](api/core/NgModule#entryComponents) | v11 | | `@angular/core` | [`ANALYZE_FOR_ENTRY_COMPONENTS`](api/core/ANALYZE_FOR_ENTRY_COMPONENTS) | v11 | +| `@angular/core` | [Factory-based signature of `ViewContainerRef.createComponent`](api/core/ViewContainerRef#createComponent) | v15 | | `@angular/router` | [`loadChildren` string syntax](#loadChildren) | v11 | | `@angular/core/testing` | [`TestBed.get`](#testing) | v12 | | `@angular/core/testing` | [`async`](#testing) | 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} diff --git a/goldens/circular-deps/packages.json b/goldens/circular-deps/packages.json index 51e1575bf05..7d093f02940 100644 --- a/goldens/circular-deps/packages.json +++ b/goldens/circular-deps/packages.json @@ -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", diff --git a/goldens/public-api/core/core.md b/goldens/public-api/core/core.md index 1af481e097d..45946e9b1b1 100644 --- a/goldens/public-api/core/core.md +++ b/goldens/public-api/core/core.md @@ -1358,7 +1358,14 @@ export interface ViewChildrenDecorator { // @public export abstract class ViewContainerRef { abstract clear(): void; - abstract createComponent(componentFactory: ComponentFactory, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef): ComponentRef; + abstract createComponent(componentType: Type, options?: { + index?: number; + injector?: Injector; + ngModuleRef?: NgModuleRef; + projectableNodes?: Node[][]; + }): ComponentRef; + // @deprecated + abstract createComponent(componentFactory: ComponentFactory, index?: number, injector?: Injector, projectableNodes?: any[][], ngModuleRef?: NgModuleRef): ComponentRef; abstract createEmbeddedView(templateRef: TemplateRef, context?: C, index?: number): EmbeddedViewRef; abstract detach(index?: number): ViewRef | null; abstract get element(): ElementRef; diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index 29153d6be34..de7f63cc861 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -15,7 +15,7 @@ "master": { "uncompressed": { "runtime-es2017": 4841, - "main-es2017": 462493, + "main-es2017": 463023, "polyfills-es2017": 55373, "styles": 69772, "light-theme": 79025, diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index f7ae0203add..4aef20588c7 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -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'; diff --git a/packages/core/src/linker/view_container_ref.ts b/packages/core/src/linker/view_container_ref.ts index bf882bd609c..223d2fddf63 100644 --- a/packages/core/src/linker/view_container_ref.ts +++ b/packages/core/src/linker/view_container_ref.ts @@ -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 + * [``](api/core/ng-content) of the new component instance. + * + * @returns The new `ComponentRef` which contains the component instance and the host view. + */ + abstract createComponent(componentType: Type, options?: { + index?: number, + injector?: Injector, + ngModuleRef?: NgModuleRef, + projectableNodes?: Node[][], + }): ComponentRef; + + /** + * 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 + * [``](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( componentFactory: ComponentFactory, index?: number, injector?: Injector, - projectableNodes?: any[][], ngModule?: NgModuleRef): ComponentRef; + projectableNodes?: any[][], ngModuleRef?: NgModuleRef): ComponentRef; /** * Inserts a view into this container. @@ -239,10 +271,77 @@ const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef { return viewRef; } + override createComponent(componentType: Type, options?: { + index?: number, + injector?: Injector, + projectableNodes?: Node[][], + ngModuleRef?: NgModuleRef, + }): ComponentRef; + /** + * @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( componentFactory: ComponentFactory, index?: number|undefined, injector?: Injector|undefined, projectableNodes?: any[][]|undefined, + ngModuleRef?: NgModuleRef|undefined): ComponentRef; + override createComponent( + componentFactoryOrType: ComponentFactory|Type, indexOrOptions?: number|undefined|{ + index?: number, + injector?: Injector, + ngModuleRef?: NgModuleRef, + projectableNodes?: Node[][], + }, + injector?: Injector|undefined, projectableNodes?: any[][]|undefined, ngModuleRef?: NgModuleRef|undefined): ComponentRef { + 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, + projectableNodes?: Node[][], + }; + index = options.index; + injector = options.injector; + projectableNodes = options.projectableNodes; + ngModuleRef = options.ngModuleRef; + } + + const componentFactory: ComponentFactory = isComponentFactory ? + componentFactoryOrType as ComponentFactory: + 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 diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index 624788f2814..121ef3a847f 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -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'; diff --git a/packages/core/src/render3/view_engine_compatibility_prebound.ts b/packages/core/src/render3/view_engine_compatibility_prebound.ts index 9e9f5bf6572..3ad181e1e0d 100644 --- a/packages/core/src/render3/view_engine_compatibility_prebound.ts +++ b/packages/core/src/render3/view_engine_compatibility_prebound.ts @@ -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'; diff --git a/packages/core/src/view/provider.ts b/packages/core/src/view/provider.ts index b696f9bfb8f..2a63193cdb8 100644 --- a/packages/core/src/view/provider.ts +++ b/packages/core/src/view/provider.ts @@ -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 { diff --git a/packages/core/src/view/provider_flags.ts b/packages/core/src/view/provider_flags.ts new file mode 100644 index 00000000000..adfc254a6ca --- /dev/null +++ b/packages/core/src/view/provider_flags.ts @@ -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 = {}; diff --git a/packages/core/src/view/refs.ts b/packages/core/src/view/refs.ts index b33327a2b68..5d393e1d2a9 100644 --- a/packages/core/src/view/refs.ts +++ b/packages/core/src/view/refs.ts @@ -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( - componentFactory: ComponentFactory, index?: number, injector?: Injector, - projectableNodes?: any[][], ngModuleRef?: NgModuleRef): ComponentRef { + componentFactoryOrType: ComponentFactory|Type, indexOrOptions?: number|{}, + injector?: Injector, projectableNodes?: any[][], + ngModuleRef?: NgModuleRef): ComponentRef { + 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; if (!ngModuleRef && !(componentFactory instanceof ComponentFactoryBoundToModule)) { ngModuleRef = contextInjector.get(NgModuleRef); } diff --git a/packages/core/test/acceptance/view_container_ref_spec.ts b/packages/core/test/acceptance/view_container_ref_spec.ts index 16b1940b6b6..aeda6f263a5 100644 --- a/packages/core/test/acceptance/view_container_ref_spec.ts +++ b/packages/core/test/acceptance/view_container_ref_spec.ts @@ -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] + + {{ 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, + public injector: Injector) {} + } + + @NgModule({ + declarations: [App, ChildA, ChildB], + providers: [ + {provide: TOKEN_A, useValue: '[TokenValueA]'}, + ] + }) + class AppModule { + } + + let fixture!: ComponentFixture; + 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', () => { diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 191225f16df..71feee2c733 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -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" },