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 {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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,17 +64,22 @@ 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);
|
||||||
|
|
||||||
|
// 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) {
|
if (!checkNoChangesMode) {
|
||||||
executeInitHooks(viewData, tView, creationMode);
|
executeInitHooks(viewData, tView, creationMode);
|
||||||
}
|
}
|
||||||
@ -86,8 +91,9 @@ function refreshDescendantViews(viewData: LViewData) {
|
|||||||
if (!checkNoChangesMode) {
|
if (!checkNoChangesMode) {
|
||||||
executeHooks(viewData, tView.contentHooks, tView.contentCheckHooks, creationMode);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -500,9 +500,6 @@
|
|||||||
{
|
{
|
||||||
"name": "executeHooks"
|
"name": "executeHooks"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "executeInitAndContentHooks"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "executeInitHooks"
|
"name": "executeInitHooks"
|
||||||
},
|
},
|
||||||
|
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -554,9 +554,6 @@
|
|||||||
{
|
{
|
||||||
"name": "executeHooks"
|
"name": "executeHooks"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "executeInitAndContentHooks"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "executeInitHooks"
|
"name": "executeInitHooks"
|
||||||
},
|
},
|
||||||
|
@ -1505,9 +1505,6 @@
|
|||||||
{
|
{
|
||||||
"name": "executeHooks"
|
"name": "executeHooks"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "executeInitAndContentHooks"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "executeInitHooks"
|
"name": "executeInitHooks"
|
||||||
},
|
},
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user