fix(ivy): dynamically created components should run init hooks (#26864)
PR Close #26864
This commit is contained in:
parent
911bfef04c
commit
a2929dfd57
|
@ -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<T>(
|
|||
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<T>(
|
|||
hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef));
|
||||
|
||||
if (tView.firstTemplatePass) prefillHostVars(tView, rootView, componentDef.hostVars);
|
||||
setHostBindings(tView, rootView);
|
||||
return component;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<T> extends viewEngine_ComponentFactory<T> {
|
|||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -64,17 +64,22 @@ 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);
|
||||
|
||||
// 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);
|
||||
|
||||
if (!checkNoChangesMode) {
|
||||
executeInitHooks(viewData, tView, creationMode);
|
||||
}
|
||||
|
@ -86,8 +91,9 @@ function refreshDescendantViews(viewData: LViewData) {
|
|||
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<T>(
|
||||
renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags,
|
||||
sanitizer?: Sanitizer | null): LViewData {
|
||||
|
@ -304,7 +301,7 @@ export function renderTemplate<T>(
|
|||
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<T>(
|
|||
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<T = any>(level: number = 1): 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 oldView = enterView(hostView, hostView[HOST_NODE]);
|
||||
try {
|
||||
|
@ -413,17 +411,9 @@ function renderComponentOrTemplate<T>(
|
|||
}
|
||||
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<T>(
|
||||
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<T>(
|
|||
// 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<T>(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<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. */
|
||||
export function detectChangesInternal<T>(hostView: LViewData, component: T) {
|
||||
function detectChangesInternal<T>(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<T>(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<T>(
|
||||
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<T>(viewQuery: ComponentQuery<{}>| null, component: T): void {
|
||||
if (viewQuery) {
|
||||
function updateViewQuery<T>(
|
||||
viewQuery: ComponentQuery<{}>| null, flags: LViewFlags, component: T): void {
|
||||
if (viewQuery && flags & RenderFlags.Update) {
|
||||
viewQuery(RenderFlags.Update, component);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -500,9 +500,6 @@
|
|||
{
|
||||
"name": "executeHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeInitAndContentHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeInitHooks"
|
||||
},
|
||||
|
|
|
@ -200,9 +200,6 @@
|
|||
{
|
||||
"name": "executeHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeInitAndContentHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeInitHooks"
|
||||
},
|
||||
|
@ -371,6 +368,9 @@
|
|||
{
|
||||
"name": "prefillHostVars"
|
||||
},
|
||||
{
|
||||
"name": "queueComponentIndexForCheck"
|
||||
},
|
||||
{
|
||||
"name": "queueHostBindingForCheck"
|
||||
},
|
||||
|
|
|
@ -3194,9 +3194,6 @@
|
|||
{
|
||||
"name": "executeHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeInitAndContentHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeInitHooks"
|
||||
},
|
||||
|
@ -4145,6 +4142,9 @@
|
|||
{
|
||||
"name": "queryDef"
|
||||
},
|
||||
{
|
||||
"name": "queueComponentIndexForCheck"
|
||||
},
|
||||
{
|
||||
"name": "queueContentHooks"
|
||||
},
|
||||
|
|
|
@ -554,9 +554,6 @@
|
|||
{
|
||||
"name": "executeHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeInitAndContentHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeInitHooks"
|
||||
},
|
||||
|
|
|
@ -1505,9 +1505,6 @@
|
|||
{
|
||||
"name": "executeHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeInitAndContentHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeInitHooks"
|
||||
},
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
/** <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', () => {
|
||||
/**
|
||||
* <comp>
|
||||
|
|
|
@ -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('<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>');
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue