From a2929dfd57bf510a8f316a90a298ee5804661e90 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Tue, 30 Oct 2018 22:10:23 -0700 Subject: [PATCH] fix(ivy): dynamically created components should run init hooks (#26864) PR Close #26864 --- packages/core/src/render3/component.ts | 7 +- packages/core/src/render3/component_ref.ts | 10 +- packages/core/src/render3/instructions.ts | 102 +++++++++--------- .../bundle.golden_symbols.json | 3 - .../hello_world/bundle.golden_symbols.json | 6 +- .../hello_world_r2/bundle.golden_symbols.json | 6 +- .../bundling/todo/bundle.golden_symbols.json | 3 - .../todo_r2/bundle.golden_symbols.json | 3 - packages/core/test/render3/lifecycle_spec.ts | 74 ++++++++++++- .../test/render3/view_container_ref_spec.ts | 83 +++++++++++++- 10 files changed, 212 insertions(+), 85 deletions(-) diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index aa7f8bae49..50ac44e53e 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -17,7 +17,7 @@ import {getComponentViewByInstance} from './context_discovery'; import {getComponentDef} from './definition'; import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di'; import {queueInitHooks, queueLifecycleHooks} from './hooks'; -import {CLEAN_PROMISE, createLViewData, createNodeAtIndex, createTView, detectChangesInternal, executeInitAndContentHooks, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, prefillHostVars, setHostBindings} from './instructions'; +import {CLEAN_PROMISE, createLViewData, createNodeAtIndex, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, prefillHostVars, queueComponentIndexForCheck, refreshDescendantViews} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; import {TElementNode, TNodeFlags, TNodeType} from './interfaces/node'; import {PlayerHandler} from './interfaces/player'; @@ -134,8 +134,7 @@ export function renderComponent( component = createRootComponent( hostRNode, componentView, componentDef, rootView, rootContext, opts.hostFeatures || null); - executeInitAndContentHooks(rootView); - detectChangesInternal(componentView, component); + refreshDescendantViews(rootView, null); } finally { leaveView(oldView); if (rendererFactory.end) rendererFactory.end(); @@ -171,6 +170,7 @@ export function createRootComponentView( diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), rootView, def.type); tNode.flags = TNodeFlags.isComponent; initNodeFlags(tNode, rootView.length, 1); + queueComponentIndexForCheck(tNode); } // Store component view at node index, with node as the HOST @@ -196,7 +196,6 @@ export function createRootComponent( hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef)); if (tView.firstTemplatePass) prefillHostVars(tView, rootView, componentDef.hostVars); - setHostBindings(tView, rootView); return component; } diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index ead3d598a9..65cae6fc9c 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -20,12 +20,12 @@ import {Type} from '../type'; import {assertComponentType, assertDefined} from './assert'; import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component'; import {getComponentDef} from './definition'; -import {createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, renderEmbeddedTemplate} from './instructions'; +import {createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions'; import {ComponentDef, RenderFlags} from './interfaces/definition'; import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {FLAGS, HEADER_OFFSET, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view'; -import {enterView} from './state'; +import {enterView, leaveView} from './state'; import {getTNode} from './util'; import {createElementRef} from './view_engine_compatibility'; import {RootViewRef, ViewRef} from './view_ref'; @@ -177,11 +177,9 @@ export class ComponentFactory extends viewEngine_ComponentFactory { hostRNode, componentView, this.componentDef, rootView, rootContext, [LifecycleHooksFeature]); - // Execute the template in creation mode only, and then turn off the CreationMode flag - renderEmbeddedTemplate(componentView, componentView[TVIEW], component, RenderFlags.Create); - componentView[FLAGS] &= ~LViewFlags.CreationMode; + refreshDescendantViews(rootView, RenderFlags.Create); } finally { - enterView(oldView, null); + leaveView(oldView, true); if (rendererFactory.end) rendererFactory.end(); } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 5fbe2c74de..2a88e01e9e 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -64,30 +64,36 @@ type SanitizerFn = (value: any) => string; * bindings, refreshes child components. * Note: view hooks are triggered later when leaving the view. */ -function refreshDescendantViews(viewData: LViewData) { +export function refreshDescendantViews(viewData: LViewData, rf: RenderFlags | null) { const tView = getTView(); - const creationMode = getCreationMode(); - const checkNoChangesMode = getCheckNoChangesMode(); - setHostBindings(tView, viewData); const parentFirstTemplatePass = getFirstTemplatePass(); // This needs to be set before children are processed to support recursive components tView.firstTemplatePass = false; setFirstTemplatePass(false); - if (!checkNoChangesMode) { - executeInitHooks(viewData, tView, creationMode); - } - refreshDynamicEmbeddedViews(viewData); + // Dynamically created views must run first only in creation mode. If this is a + // creation-only pass, we should not call lifecycle hooks or evaluate bindings. + // This will be done in the update-only pass. + if (rf !== RenderFlags.Create) { + const creationMode = getCreationMode(); + const checkNoChangesMode = getCheckNoChangesMode(); + setHostBindings(tView, viewData); - // Content query results must be refreshed before content hooks are called. - refreshContentQueries(tView); + if (!checkNoChangesMode) { + executeInitHooks(viewData, tView, creationMode); + } + refreshDynamicEmbeddedViews(viewData); - if (!checkNoChangesMode) { - executeHooks(viewData, tView.contentHooks, tView.contentCheckHooks, creationMode); + // Content query results must be refreshed before content hooks are called. + refreshContentQueries(tView); + + if (!checkNoChangesMode) { + executeHooks(viewData, tView.contentHooks, tView.contentCheckHooks, creationMode); + } } - refreshChildComponents(tView.components, parentFirstTemplatePass); + refreshChildComponents(tView.components, parentFirstTemplatePass, rf); } @@ -144,23 +150,14 @@ function refreshContentQueries(tView: TView): void { /** Refreshes child components in the current view. */ function refreshChildComponents( - components: number[] | null, parentFirstTemplatePass: boolean): void { + components: number[] | null, parentFirstTemplatePass: boolean, rf: RenderFlags | null): void { if (components != null) { for (let i = 0; i < components.length; i++) { - componentRefresh(components[i], parentFirstTemplatePass); + componentRefresh(components[i], parentFirstTemplatePass, rf); } } } -export function executeInitAndContentHooks(viewData: LViewData): void { - if (!getCheckNoChangesMode()) { - const tView = getTView(); - const creationMode = getCreationMode(); - executeInitHooks(viewData, tView, creationMode); - executeHooks(viewData, tView.contentHooks, tView.contentCheckHooks, creationMode); - } -} - export function createLViewData( renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags, sanitizer?: Sanitizer | null): LViewData { @@ -304,7 +301,7 @@ export function renderTemplate( createLViewData(renderer, componentTView, context, LViewFlags.CheckAlways, sanitizer); hostView[HOST_NODE] = createNodeAtIndex(0, TNodeType.Element, hostNode, null, null); } - renderComponentOrTemplate(hostView, context, templateFn); + renderComponentOrTemplate(hostView, context, null, templateFn); return hostView; } @@ -369,7 +366,7 @@ export function renderEmbeddedTemplate( namespaceHTML(); tView.template !(rf, context); if (rf & RenderFlags.Update) { - refreshDescendantViews(viewToRender); + refreshDescendantViews(viewToRender, null); } else { // This must be set to false immediately after the first creation run because in an // ngFor loop, all the views will be created together before update mode runs and turns @@ -404,7 +401,8 @@ export function nextContext(level: number = 1): T { } function renderComponentOrTemplate( - hostView: LViewData, componentOrContext: T, templateFn?: ComponentTemplate) { + hostView: LViewData, componentOrContext: T, rf: RenderFlags | null, + templateFn?: ComponentTemplate) { const rendererFactory = getRendererFactory(); const oldView = enterView(hostView, hostView[HOST_NODE]); try { @@ -413,17 +411,9 @@ function renderComponentOrTemplate( } if (templateFn) { namespaceHTML(); - templateFn(getRenderFlags(hostView), componentOrContext !); - refreshDescendantViews(hostView); - } else { - executeInitAndContentHooks(hostView); - - // Element was stored at 0 in data and directive was stored at 0 in directives - // in renderComponent() - setHostBindings(getTView(), hostView); - refreshDynamicEmbeddedViews(hostView); - componentRefresh(HEADER_OFFSET, false); + templateFn(rf || getRenderFlags(hostView), componentOrContext !); } + refreshDescendantViews(hostView, rf); } finally { if (rendererFactory.end) { rendererFactory.end(); @@ -1488,7 +1478,7 @@ function findDirectiveMatches(tView: TView, viewData: LViewData, tNode: TNode): } /** Stores index of component's host element so it will be queued for view refresh during CD. */ -function queueComponentIndexForCheck(previousOrParentTNode: TNode): void { +export function queueComponentIndexForCheck(previousOrParentTNode: TNode): void { ngDevMode && assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.'); const tView = getTView(); @@ -1829,7 +1819,7 @@ export function containerRefreshEnd(): void { * Goes over dynamic embedded views (ones created through ViewContainerRef APIs) and refreshes them * by executing an associated template function. */ -export function refreshDynamicEmbeddedViews(lViewData: LViewData) { +function refreshDynamicEmbeddedViews(lViewData: LViewData) { for (let current = getLViewChild(lViewData); current !== null; current = current[NEXT]) { // Note: current can be an LViewData or an LContainer instance, but here we are only interested // in LContainer. We can tell it's an LContainer because its length is less than the LViewData @@ -1957,7 +1947,7 @@ function getOrCreateEmbeddedTView( export function embeddedViewEnd(): void { const viewData = getViewData(); const viewHost = viewData[HOST_NODE]; - refreshDescendantViews(viewData); + refreshDescendantViews(viewData, null); leaveView(viewData[PARENT] !); setPreviousOrParentTNode(viewHost !); setIsParent(false); @@ -1971,7 +1961,7 @@ export function embeddedViewEnd(): void { * @param adjustedElementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET) */ export function componentRefresh( - adjustedElementIndex: number, parentFirstTemplatePass: boolean): void { + adjustedElementIndex: number, parentFirstTemplatePass: boolean, rf: RenderFlags | null): void { ngDevMode && assertDataInRange(adjustedElementIndex); const hostView = getComponentViewByIndex(adjustedElementIndex, getViewData()); ngDevMode && assertNodeType(getTView().data[adjustedElementIndex] as TNode, TNodeType.Element); @@ -1979,7 +1969,7 @@ export function componentRefresh( // Only attached CheckAlways components or attached, dirty OnPush components should be checked if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) { parentFirstTemplatePass && syncViewWithBlueprint(hostView); - detectChangesInternal(hostView, hostView[CONTEXT]); + detectChangesInternal(hostView, hostView[CONTEXT], rf); } } @@ -2261,7 +2251,8 @@ export function tick(component: T): void { function tickRootContext(rootContext: RootContext) { for (let i = 0; i < rootContext.components.length; i++) { const rootComponent = rootContext.components[i]; - renderComponentOrTemplate(readPatchedLViewData(rootComponent) !, rootComponent); + renderComponentOrTemplate( + readPatchedLViewData(rootComponent) !, rootComponent, RenderFlags.Update); } } @@ -2279,7 +2270,7 @@ function tickRootContext(rootContext: RootContext) { * @param component The component which the change detection should be performed on. */ export function detectChanges(component: T): void { - detectChangesInternal(getComponentViewByInstance(component) !, component); + detectChangesInternal(getComponentViewByInstance(component) !, component, null); } /** @@ -2326,7 +2317,7 @@ export function checkNoChangesInRootView(lViewData: LViewData): void { } /** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */ -export function detectChangesInternal(hostView: LViewData, component: T) { +function detectChangesInternal(hostView: LViewData, component: T, rf: RenderFlags | null) { const hostTView = hostView[TVIEW]; const oldView = enterView(hostView, hostView[HOST_NODE]); const templateFn = hostTView.template !; @@ -2334,24 +2325,27 @@ export function detectChangesInternal(hostView: LViewData, component: T) { try { namespaceHTML(); - createViewQuery(viewQuery, hostView[FLAGS], component); - templateFn(getRenderFlags(hostView), component); - refreshDescendantViews(hostView); - updateViewQuery(viewQuery, component); + createViewQuery(viewQuery, rf, hostView[FLAGS], component); + templateFn(rf || getRenderFlags(hostView), component); + refreshDescendantViews(hostView, rf); + updateViewQuery(viewQuery, hostView[FLAGS], component); } finally { - leaveView(oldView); + leaveView(oldView, rf === RenderFlags.Create); } } function createViewQuery( - viewQuery: ComponentQuery<{}>| null, flags: LViewFlags, component: T): void { - if (viewQuery && (flags & LViewFlags.CreationMode)) { + viewQuery: ComponentQuery<{}>| null, renderFlags: RenderFlags | null, viewFlags: LViewFlags, + component: T): void { + if (viewQuery && (renderFlags === RenderFlags.Create || + (renderFlags === null && (viewFlags & LViewFlags.CreationMode)))) { viewQuery(RenderFlags.Create, component); } } -function updateViewQuery(viewQuery: ComponentQuery<{}>| null, component: T): void { - if (viewQuery) { +function updateViewQuery( + viewQuery: ComponentQuery<{}>| null, flags: LViewFlags, component: T): void { + if (viewQuery && flags & RenderFlags.Update) { viewQuery(RenderFlags.Update, component); } } diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index 01881d8832..8f9a6ef688 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -500,9 +500,6 @@ { "name": "executeHooks" }, - { - "name": "executeInitAndContentHooks" - }, { "name": "executeInitHooks" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index ccccc901c6..627588631d 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -200,9 +200,6 @@ { "name": "executeHooks" }, - { - "name": "executeInitAndContentHooks" - }, { "name": "executeInitHooks" }, @@ -371,6 +368,9 @@ { "name": "prefillHostVars" }, + { + "name": "queueComponentIndexForCheck" + }, { "name": "queueHostBindingForCheck" }, diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index 59592afbb6..2ee3b4f8ad 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -3194,9 +3194,6 @@ { "name": "executeHooks" }, - { - "name": "executeInitAndContentHooks" - }, { "name": "executeInitHooks" }, @@ -4145,6 +4142,9 @@ { "name": "queryDef" }, + { + "name": "queueComponentIndexForCheck" + }, { "name": "queueContentHooks" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index e80290b619..4750ece4a0 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -554,9 +554,6 @@ { "name": "executeHooks" }, - { - "name": "executeInitAndContentHooks" - }, { "name": "executeInitHooks" }, diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 7667eda429..797f265634 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -1505,9 +1505,6 @@ { "name": "executeHooks" }, - { - "name": "executeInitAndContentHooks" - }, { "name": "executeInitHooks" }, diff --git a/packages/core/test/render3/lifecycle_spec.ts b/packages/core/test/render3/lifecycle_spec.ts index b011d0bf18..c6f1a581fc 100644 --- a/packages/core/test/render3/lifecycle_spec.ts +++ b/packages/core/test/render3/lifecycle_spec.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {OnDestroy, SimpleChanges} from '../../src/core'; -import {AttributeMarker, ComponentTemplate, LifecycleHooksFeature, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective} from '../../src/render3/index'; +import {ComponentFactoryResolver, OnDestroy, SimpleChanges, ViewContainerRef} from '../../src/core'; +import {AttributeMarker, ComponentTemplate, LifecycleHooksFeature, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective, injectComponentFactoryResolver} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, listener, markDirty, projection, projectionDef, store, template, text} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, listener, markDirty, projection, projectionDef, store, template, text} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {NgIf} from './common_with_def'; @@ -77,7 +77,7 @@ describe('lifecycles', () => { {type: Directive, selectors: [['', 'dir', '']], factory: () => new Directive()}); } - const directives = [Comp, Parent, ProjectedComp, Directive]; + const directives = [Comp, Parent, ProjectedComp, Directive, NgIf]; it('should call onInit method after inputs are set in creation mode (and not in update mode)', () => { @@ -184,6 +184,72 @@ describe('lifecycles', () => { expect(events).toEqual(['comp', 'comp']); }); + + it('should call onInit every time a new view is created (ngIf)', () => { + + function IfTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(0, 'comp'); + } + } + + /** */ + const App = createComponent('app', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + template(0, IfTemplate, 1, 0, '', ['ngIf', '']); + } + if (rf & RenderFlags.Update) { + elementProperty(0, 'ngIf', bind(ctx.showing)); + } + }, 1, 0, directives); + + const fixture = new ComponentFixture(App); + + fixture.component.showing = true; + fixture.update(); + expect(events).toEqual(['comp']); + + fixture.component.showing = false; + fixture.update(); + expect(events).toEqual(['comp']); + + fixture.component.showing = true; + fixture.update(); + expect(events).toEqual(['comp', 'comp']); + }); + + it('should call onInit for children of dynamically created components', () => { + let viewContainerComp !: ViewContainerComp; + + class ViewContainerComp { + constructor(public vcr: ViewContainerRef, public cfr: ComponentFactoryResolver) {} + + static ngComponentDef = defineComponent({ + type: ViewContainerComp, + selectors: [['view-container-comp']], + factory: () => viewContainerComp = new ViewContainerComp( + directiveInject(ViewContainerRef as any), injectComponentFactoryResolver()), + consts: 0, + vars: 0, + template: (rf: RenderFlags, ctx: ViewContainerComp) => {} + }); + } + + const DynamicComp = createComponent('dynamic-comp', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + element(0, 'comp'); + } + }, 1, 0, [Comp]); + + const fixture = new ComponentFixture(ViewContainerComp); + expect(events).toEqual([]); + + viewContainerComp.vcr.createComponent( + viewContainerComp.cfr.resolveComponentFactory(DynamicComp)); + fixture.update(); + expect(events).toEqual(['comp']); + }); + it('should call onInit in hosts before their content children', () => { /** * diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index e1069f398f..2469dcc168 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -6,15 +6,18 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core'; +import {Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, QueryList, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core'; import {ViewEncapsulation} from '../../src/metadata'; -import {AttributeMarker, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, load} from '../../src/render3/index'; +import {AttributeMarker, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, load, query, queryRefresh} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; +import {RElement} from '../../src/render3/interfaces/renderer'; import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; import {NgModuleFactory} from '../../src/render3/ng_module_ref'; import {pipe, pipeBind1} from '../../src/render3/pipe'; +import {getViewData} from '../../src/render3/state'; +import {getNativeByIndex} from '../../src/render3/util'; import {NgForOf} from '../../test/render3/common_with_def'; import {getRendererFactory2} from './imported_renderer2'; @@ -1759,6 +1762,7 @@ describe('ViewContainerRef', () => { const componentRef = directiveInstance !.vcref.createComponent( directiveInstance !.cfr.resolveComponentFactory(HostBindingCmpt)); + fixture.update(); expect(fixture.html).toBe(''); @@ -1856,5 +1860,80 @@ describe('ViewContainerRef', () => { '
check count: 2'); }); + it('should create deep DOM tree immediately for dynamically created components', () => { + let name = 'text'; + const Child = createComponent('child', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + { text(1); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + textBinding(1, bind(name)); + } + }, 2, 1); + + const DynamicCompWithChildren = + createComponent('dynamic-cmpt-with-children', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + element(0, 'child'); + } + }, 1, 0, [Child]); + + const fixture = new ComponentFixture(AppCmpt); + expect(fixture.outerHtml).toBe('
'); + + fixture.component.insert(DynamicCompWithChildren); + expect(fixture.outerHtml) + .toBe( + '
'); + + fixture.update(); + expect(fixture.outerHtml) + .toBe( + '
text
'); + }); + + it('should support view queries for dynamically created components', () => { + let dynamicComp !: DynamicCompWithViewQueries; + let fooEl !: RElement; + + class DynamicCompWithViewQueries { + // @ViewChildren('foo') + foo !: QueryList; + + static ngComponentDef = defineComponent({ + type: DynamicCompWithViewQueries, + selectors: [['dynamic-cmpt-with-view-queries']], + factory: () => dynamicComp = new DynamicCompWithViewQueries(), + consts: 2, + vars: 0, + template: (rf: RenderFlags, ctx: DynamicCompWithViewQueries) => { + if (rf & RenderFlags.Create) { + element(1, 'div', ['bar', ''], ['foo', '']); + } + // testing only + fooEl = getNativeByIndex(1, getViewData()); + }, + viewQuery: function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.foo = tmp as QueryList); + } + } + }); + } + + const fixture = new ComponentFixture(AppCmpt); + + fixture.component.insert(DynamicCompWithViewQueries); + fixture.update(); + + expect(dynamicComp.foo.first.nativeElement).toEqual(fooEl as any); + }); + }); });