fix(ivy): dynamically created components should run init hooks (#26864)

PR Close #26864
This commit is contained in:
Kara Erickson 2018-10-30 22:10:23 -07:00
parent 911bfef04c
commit a2929dfd57
10 changed files with 212 additions and 85 deletions

View File

@ -17,7 +17,7 @@ import {getComponentViewByInstance} from './context_discovery';
import {getComponentDef} from './definition'; import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di'; import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {queueInitHooks, queueLifecycleHooks} from './hooks'; 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 {ComponentDef, ComponentType} from './interfaces/definition';
import {TElementNode, TNodeFlags, TNodeType} from './interfaces/node'; import {TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player'; import {PlayerHandler} from './interfaces/player';
@ -134,8 +134,7 @@ export function renderComponent<T>(
component = createRootComponent( component = createRootComponent(
hostRNode, componentView, componentDef, rootView, rootContext, opts.hostFeatures || null); hostRNode, componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
executeInitAndContentHooks(rootView); refreshDescendantViews(rootView, null);
detectChangesInternal(componentView, component);
} finally { } finally {
leaveView(oldView); leaveView(oldView);
if (rendererFactory.end) rendererFactory.end(); if (rendererFactory.end) rendererFactory.end();
@ -171,6 +170,7 @@ export function createRootComponentView(
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), rootView, def.type); diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), rootView, def.type);
tNode.flags = TNodeFlags.isComponent; tNode.flags = TNodeFlags.isComponent;
initNodeFlags(tNode, rootView.length, 1); initNodeFlags(tNode, rootView.length, 1);
queueComponentIndexForCheck(tNode);
} }
// Store component view at node index, with node as the HOST // Store component view at node index, with node as the HOST
@ -196,7 +196,6 @@ export function createRootComponent<T>(
hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef)); hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef));
if (tView.firstTemplatePass) prefillHostVars(tView, rootView, componentDef.hostVars); if (tView.firstTemplatePass) prefillHostVars(tView, rootView, componentDef.hostVars);
setHostBindings(tView, rootView);
return component; return component;
} }

View File

@ -20,12 +20,12 @@ import {Type} from '../type';
import {assertComponentType, assertDefined} from './assert'; import {assertComponentType, assertDefined} from './assert';
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component'; import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
import {getComponentDef} from './definition'; 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 {ComponentDef, RenderFlags} from './interfaces/definition';
import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {FLAGS, HEADER_OFFSET, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view'; 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 {getTNode} from './util';
import {createElementRef} from './view_engine_compatibility'; import {createElementRef} from './view_engine_compatibility';
import {RootViewRef, ViewRef} from './view_ref'; import {RootViewRef, ViewRef} from './view_ref';
@ -177,11 +177,9 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
hostRNode, componentView, this.componentDef, rootView, rootContext, hostRNode, componentView, this.componentDef, rootView, rootContext,
[LifecycleHooksFeature]); [LifecycleHooksFeature]);
// Execute the template in creation mode only, and then turn off the CreationMode flag refreshDescendantViews(rootView, RenderFlags.Create);
renderEmbeddedTemplate(componentView, componentView[TVIEW], component, RenderFlags.Create);
componentView[FLAGS] &= ~LViewFlags.CreationMode;
} finally { } finally {
enterView(oldView, null); leaveView(oldView, true);
if (rendererFactory.end) rendererFactory.end(); if (rendererFactory.end) rendererFactory.end();
} }

View File

@ -64,30 +64,36 @@ type SanitizerFn = (value: any) => string;
* bindings, refreshes child components. * bindings, refreshes child components.
* Note: view hooks are triggered later when leaving the view. * 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 tView = getTView();
const creationMode = getCreationMode();
const checkNoChangesMode = getCheckNoChangesMode();
setHostBindings(tView, viewData);
const parentFirstTemplatePass = getFirstTemplatePass(); const parentFirstTemplatePass = getFirstTemplatePass();
// This needs to be set before children are processed to support recursive components // This needs to be set before children are processed to support recursive components
tView.firstTemplatePass = false; tView.firstTemplatePass = false;
setFirstTemplatePass(false); setFirstTemplatePass(false);
if (!checkNoChangesMode) { // Dynamically created views must run first only in creation mode. If this is a
executeInitHooks(viewData, tView, creationMode); // creation-only pass, we should not call lifecycle hooks or evaluate bindings.
} // This will be done in the update-only pass.
refreshDynamicEmbeddedViews(viewData); if (rf !== RenderFlags.Create) {
const creationMode = getCreationMode();
const checkNoChangesMode = getCheckNoChangesMode();
setHostBindings(tView, viewData);
// Content query results must be refreshed before content hooks are called. if (!checkNoChangesMode) {
refreshContentQueries(tView); executeInitHooks(viewData, tView, creationMode);
}
refreshDynamicEmbeddedViews(viewData);
if (!checkNoChangesMode) { // Content query results must be refreshed before content hooks are called.
executeHooks(viewData, tView.contentHooks, tView.contentCheckHooks, creationMode); 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. */ /** Refreshes child components in the current view. */
function refreshChildComponents( function refreshChildComponents(
components: number[] | null, parentFirstTemplatePass: boolean): void { components: number[] | null, parentFirstTemplatePass: boolean, rf: RenderFlags | null): void {
if (components != null) { if (components != null) {
for (let i = 0; i < components.length; i++) { 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<T>( export function createLViewData<T>(
renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags, renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags,
sanitizer?: Sanitizer | null): LViewData { sanitizer?: Sanitizer | null): LViewData {
@ -304,7 +301,7 @@ export function renderTemplate<T>(
createLViewData(renderer, componentTView, context, LViewFlags.CheckAlways, sanitizer); createLViewData(renderer, componentTView, context, LViewFlags.CheckAlways, sanitizer);
hostView[HOST_NODE] = createNodeAtIndex(0, TNodeType.Element, hostNode, null, null); hostView[HOST_NODE] = createNodeAtIndex(0, TNodeType.Element, hostNode, null, null);
} }
renderComponentOrTemplate(hostView, context, templateFn); renderComponentOrTemplate(hostView, context, null, templateFn);
return hostView; return hostView;
} }
@ -369,7 +366,7 @@ export function renderEmbeddedTemplate<T>(
namespaceHTML(); namespaceHTML();
tView.template !(rf, context); tView.template !(rf, context);
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
refreshDescendantViews(viewToRender); refreshDescendantViews(viewToRender, null);
} else { } else {
// This must be set to false immediately after the first creation run because in an // 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 // ngFor loop, all the views will be created together before update mode runs and turns
@ -404,7 +401,8 @@ export function nextContext<T = any>(level: number = 1): T {
} }
function renderComponentOrTemplate<T>( function renderComponentOrTemplate<T>(
hostView: LViewData, componentOrContext: T, templateFn?: ComponentTemplate<T>) { hostView: LViewData, componentOrContext: T, rf: RenderFlags | null,
templateFn?: ComponentTemplate<T>) {
const rendererFactory = getRendererFactory(); const rendererFactory = getRendererFactory();
const oldView = enterView(hostView, hostView[HOST_NODE]); const oldView = enterView(hostView, hostView[HOST_NODE]);
try { try {
@ -413,17 +411,9 @@ function renderComponentOrTemplate<T>(
} }
if (templateFn) { if (templateFn) {
namespaceHTML(); namespaceHTML();
templateFn(getRenderFlags(hostView), componentOrContext !); templateFn(rf || 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);
} }
refreshDescendantViews(hostView, rf);
} finally { } finally {
if (rendererFactory.end) { if (rendererFactory.end) {
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. */ /** 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 && ngDevMode &&
assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.'); assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.');
const tView = getTView(); const tView = getTView();
@ -1829,7 +1819,7 @@ export function containerRefreshEnd(): void {
* Goes over dynamic embedded views (ones created through ViewContainerRef APIs) and refreshes them * Goes over dynamic embedded views (ones created through ViewContainerRef APIs) and refreshes them
* by executing an associated template function. * 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]) { 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 // 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 // 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 { export function embeddedViewEnd(): void {
const viewData = getViewData(); const viewData = getViewData();
const viewHost = viewData[HOST_NODE]; const viewHost = viewData[HOST_NODE];
refreshDescendantViews(viewData); refreshDescendantViews(viewData, null);
leaveView(viewData[PARENT] !); leaveView(viewData[PARENT] !);
setPreviousOrParentTNode(viewHost !); setPreviousOrParentTNode(viewHost !);
setIsParent(false); setIsParent(false);
@ -1971,7 +1961,7 @@ export function embeddedViewEnd(): void {
* @param adjustedElementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET) * @param adjustedElementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET)
*/ */
export function componentRefresh<T>( export function componentRefresh<T>(
adjustedElementIndex: number, parentFirstTemplatePass: boolean): void { adjustedElementIndex: number, parentFirstTemplatePass: boolean, rf: RenderFlags | null): void {
ngDevMode && assertDataInRange(adjustedElementIndex); ngDevMode && assertDataInRange(adjustedElementIndex);
const hostView = getComponentViewByIndex(adjustedElementIndex, getViewData()); const hostView = getComponentViewByIndex(adjustedElementIndex, getViewData());
ngDevMode && assertNodeType(getTView().data[adjustedElementIndex] as TNode, TNodeType.Element); ngDevMode && assertNodeType(getTView().data[adjustedElementIndex] as TNode, TNodeType.Element);
@ -1979,7 +1969,7 @@ export function componentRefresh<T>(
// Only attached CheckAlways components or attached, dirty OnPush components should be checked // Only attached CheckAlways components or attached, dirty OnPush components should be checked
if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) { if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
parentFirstTemplatePass && syncViewWithBlueprint(hostView); parentFirstTemplatePass && syncViewWithBlueprint(hostView);
detectChangesInternal(hostView, hostView[CONTEXT]); detectChangesInternal(hostView, hostView[CONTEXT], rf);
} }
} }
@ -2261,7 +2251,8 @@ export function tick<T>(component: T): void {
function tickRootContext(rootContext: RootContext) { function tickRootContext(rootContext: RootContext) {
for (let i = 0; i < rootContext.components.length; i++) { for (let i = 0; i < rootContext.components.length; i++) {
const rootComponent = rootContext.components[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. * @param component The component which the change detection should be performed on.
*/ */
export function detectChanges<T>(component: T): void { export function detectChanges<T>(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. */ /** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */
export function detectChangesInternal<T>(hostView: LViewData, component: T) { function detectChangesInternal<T>(hostView: LViewData, component: T, rf: RenderFlags | null) {
const hostTView = hostView[TVIEW]; const hostTView = hostView[TVIEW];
const oldView = enterView(hostView, hostView[HOST_NODE]); const oldView = enterView(hostView, hostView[HOST_NODE]);
const templateFn = hostTView.template !; const templateFn = hostTView.template !;
@ -2334,24 +2325,27 @@ export function detectChangesInternal<T>(hostView: LViewData, component: T) {
try { try {
namespaceHTML(); namespaceHTML();
createViewQuery(viewQuery, hostView[FLAGS], component); createViewQuery(viewQuery, rf, hostView[FLAGS], component);
templateFn(getRenderFlags(hostView), component); templateFn(rf || getRenderFlags(hostView), component);
refreshDescendantViews(hostView); refreshDescendantViews(hostView, rf);
updateViewQuery(viewQuery, component); updateViewQuery(viewQuery, hostView[FLAGS], component);
} finally { } finally {
leaveView(oldView); leaveView(oldView, rf === RenderFlags.Create);
} }
} }
function createViewQuery<T>( function createViewQuery<T>(
viewQuery: ComponentQuery<{}>| null, flags: LViewFlags, component: T): void { viewQuery: ComponentQuery<{}>| null, renderFlags: RenderFlags | null, viewFlags: LViewFlags,
if (viewQuery && (flags & LViewFlags.CreationMode)) { component: T): void {
if (viewQuery && (renderFlags === RenderFlags.Create ||
(renderFlags === null && (viewFlags & LViewFlags.CreationMode)))) {
viewQuery(RenderFlags.Create, component); viewQuery(RenderFlags.Create, component);
} }
} }
function updateViewQuery<T>(viewQuery: ComponentQuery<{}>| null, component: T): void { function updateViewQuery<T>(
if (viewQuery) { viewQuery: ComponentQuery<{}>| null, flags: LViewFlags, component: T): void {
if (viewQuery && flags & RenderFlags.Update) {
viewQuery(RenderFlags.Update, component); viewQuery(RenderFlags.Update, component);
} }
} }

View File

@ -500,9 +500,6 @@
{ {
"name": "executeHooks" "name": "executeHooks"
}, },
{
"name": "executeInitAndContentHooks"
},
{ {
"name": "executeInitHooks" "name": "executeInitHooks"
}, },

View File

@ -200,9 +200,6 @@
{ {
"name": "executeHooks" "name": "executeHooks"
}, },
{
"name": "executeInitAndContentHooks"
},
{ {
"name": "executeInitHooks" "name": "executeInitHooks"
}, },
@ -371,6 +368,9 @@
{ {
"name": "prefillHostVars" "name": "prefillHostVars"
}, },
{
"name": "queueComponentIndexForCheck"
},
{ {
"name": "queueHostBindingForCheck" "name": "queueHostBindingForCheck"
}, },

View File

@ -3194,9 +3194,6 @@
{ {
"name": "executeHooks" "name": "executeHooks"
}, },
{
"name": "executeInitAndContentHooks"
},
{ {
"name": "executeInitHooks" "name": "executeInitHooks"
}, },
@ -4145,6 +4142,9 @@
{ {
"name": "queryDef" "name": "queryDef"
}, },
{
"name": "queueComponentIndexForCheck"
},
{ {
"name": "queueContentHooks" "name": "queueContentHooks"
}, },

View File

@ -554,9 +554,6 @@
{ {
"name": "executeHooks" "name": "executeHooks"
}, },
{
"name": "executeInitAndContentHooks"
},
{ {
"name": "executeInitHooks" "name": "executeInitHooks"
}, },

View File

@ -1505,9 +1505,6 @@
{ {
"name": "executeHooks" "name": "executeHooks"
}, },
{
"name": "executeInitAndContentHooks"
},
{ {
"name": "executeInitHooks" "name": "executeInitHooks"
}, },

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {OnDestroy, SimpleChanges} from '../../src/core'; import {ComponentFactoryResolver, OnDestroy, SimpleChanges, ViewContainerRef} from '../../src/core';
import {AttributeMarker, ComponentTemplate, LifecycleHooksFeature, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective} from '../../src/render3/index'; 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 {RenderFlags} from '../../src/render3/interfaces/definition';
import {NgIf} from './common_with_def'; import {NgIf} from './common_with_def';
@ -77,7 +77,7 @@ describe('lifecycles', () => {
{type: Directive, selectors: [['', 'dir', '']], factory: () => new Directive()}); {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)', 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']); 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');
}
}
/** <comp *ngIf="showing"></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', () => { it('should call onInit in hosts before their content children', () => {
/** /**
* <comp> * <comp>

View File

@ -6,15 +6,18 @@
* found in the LICENSE file at https://angular.io/license * 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 {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 {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 {RenderFlags} from '../../src/render3/interfaces/definition';
import {RElement} from '../../src/render3/interfaces/renderer';
import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound';
import {NgModuleFactory} from '../../src/render3/ng_module_ref'; import {NgModuleFactory} from '../../src/render3/ng_module_ref';
import {pipe, pipeBind1} from '../../src/render3/pipe'; 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 {NgForOf} from '../../test/render3/common_with_def';
import {getRendererFactory2} from './imported_renderer2'; import {getRendererFactory2} from './imported_renderer2';
@ -1759,6 +1762,7 @@ describe('ViewContainerRef', () => {
const componentRef = directiveInstance !.vcref.createComponent( const componentRef = directiveInstance !.vcref.createComponent(
directiveInstance !.cfr.resolveComponentFactory(HostBindingCmpt)); directiveInstance !.cfr.resolveComponentFactory(HostBindingCmpt));
fixture.update();
expect(fixture.html).toBe('<host-bindings id="attribute" title="initial"></host-bindings>'); expect(fixture.html).toBe('<host-bindings id="attribute" title="initial"></host-bindings>');
@ -1856,5 +1860,80 @@ describe('ViewContainerRef', () => {
'<div host="mark"></div><dynamic-cmpt-with-bindings>check count: 2</dynamic-cmpt-with-bindings>'); '<div host="mark"></div><dynamic-cmpt-with-bindings>check count: 2</dynamic-cmpt-with-bindings>');
}); });
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('<div host="mark"></div>');
fixture.component.insert(DynamicCompWithChildren);
expect(fixture.outerHtml)
.toBe(
'<div host="mark"></div><dynamic-cmpt-with-children><child><div></div></child></dynamic-cmpt-with-children>');
fixture.update();
expect(fixture.outerHtml)
.toBe(
'<div host="mark"></div><dynamic-cmpt-with-children><child><div>text</div></child></dynamic-cmpt-with-children>');
});
it('should support view queries for dynamically created components', () => {
let dynamicComp !: DynamicCompWithViewQueries;
let fooEl !: RElement;
class DynamicCompWithViewQueries {
// @ViewChildren('foo')
foo !: QueryList<any>;
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<QueryList<any>>(0)) && (ctx.foo = tmp as QueryList<any>);
}
}
});
}
const fixture = new ComponentFixture(AppCmpt);
fixture.component.insert(DynamicCompWithViewQueries);
fixture.update();
expect(dynamicComp.foo.first.nativeElement).toEqual(fooEl as any);
});
}); });
}); });