Skip to content

Commit a2929df

Browse files
committed
fix(ivy): dynamically created components should run init hooks (angular#26864)
PR Close angular#26864
1 parent 911bfef commit a2929df

File tree

10 files changed

+212
-85
lines changed

10 files changed

+212
-85
lines changed

packages/core/src/render3/component.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {getComponentViewByInstance} from './context_discovery';
1717
import {getComponentDef} from './definition';
1818
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
1919
import {queueInitHooks, queueLifecycleHooks} from './hooks';
20-
import {CLEAN_PROMISE, createLViewData, createNodeAtIndex, createTView, detectChangesInternal, executeInitAndContentHooks, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, prefillHostVars, setHostBindings} from './instructions';
20+
import {CLEAN_PROMISE, createLViewData, createNodeAtIndex, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, prefillHostVars, queueComponentIndexForCheck, refreshDescendantViews} from './instructions';
2121
import {ComponentDef, ComponentType} from './interfaces/definition';
2222
import {TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
2323
import {PlayerHandler} from './interfaces/player';
@@ -134,8 +134,7 @@ export function renderComponent<T>(
134134
component = createRootComponent(
135135
hostRNode, componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
136136

137-
executeInitAndContentHooks(rootView);
138-
detectChangesInternal(componentView, component);
137+
refreshDescendantViews(rootView, null);
139138
} finally {
140139
leaveView(oldView);
141140
if (rendererFactory.end) rendererFactory.end();
@@ -171,6 +170,7 @@ export function createRootComponentView(
171170
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), rootView, def.type);
172171
tNode.flags = TNodeFlags.isComponent;
173172
initNodeFlags(tNode, rootView.length, 1);
173+
queueComponentIndexForCheck(tNode);
174174
}
175175

176176
// Store component view at node index, with node as the HOST
@@ -196,7 +196,6 @@ export function createRootComponent<T>(
196196
hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef));
197197

198198
if (tView.firstTemplatePass) prefillHostVars(tView, rootView, componentDef.hostVars);
199-
setHostBindings(tView, rootView);
200199
return component;
201200
}
202201

packages/core/src/render3/component_ref.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ import {Type} from '../type';
2020
import {assertComponentType, assertDefined} from './assert';
2121
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
2222
import {getComponentDef} from './definition';
23-
import {createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, renderEmbeddedTemplate} from './instructions';
23+
import {createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions';
2424
import {ComponentDef, RenderFlags} from './interfaces/definition';
2525
import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
2626
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
2727
import {FLAGS, HEADER_OFFSET, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
28-
import {enterView} from './state';
28+
import {enterView, leaveView} from './state';
2929
import {getTNode} from './util';
3030
import {createElementRef} from './view_engine_compatibility';
3131
import {RootViewRef, ViewRef} from './view_ref';
@@ -177,11 +177,9 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
177177
hostRNode, componentView, this.componentDef, rootView, rootContext,
178178
[LifecycleHooksFeature]);
179179

180-
// Execute the template in creation mode only, and then turn off the CreationMode flag
181-
renderEmbeddedTemplate(componentView, componentView[TVIEW], component, RenderFlags.Create);
182-
componentView[FLAGS] &= ~LViewFlags.CreationMode;
180+
refreshDescendantViews(rootView, RenderFlags.Create);
183181
} finally {
184-
enterView(oldView, null);
182+
leaveView(oldView, true);
185183
if (rendererFactory.end) rendererFactory.end();
186184
}
187185

packages/core/src/render3/instructions.ts

Lines changed: 48 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -64,30 +64,36 @@ type SanitizerFn = (value: any) => string;
6464
* bindings, refreshes child components.
6565
* Note: view hooks are triggered later when leaving the view.
6666
*/
67-
function refreshDescendantViews(viewData: LViewData) {
67+
export function refreshDescendantViews(viewData: LViewData, rf: RenderFlags | null) {
6868
const tView = getTView();
69-
const creationMode = getCreationMode();
70-
const checkNoChangesMode = getCheckNoChangesMode();
71-
setHostBindings(tView, viewData);
7269
const parentFirstTemplatePass = getFirstTemplatePass();
7370

7471
// This needs to be set before children are processed to support recursive components
7572
tView.firstTemplatePass = false;
7673
setFirstTemplatePass(false);
7774

78-
if (!checkNoChangesMode) {
79-
executeInitHooks(viewData, tView, creationMode);
80-
}
81-
refreshDynamicEmbeddedViews(viewData);
75+
// Dynamically created views must run first only in creation mode. If this is a
76+
// creation-only pass, we should not call lifecycle hooks or evaluate bindings.
77+
// This will be done in the update-only pass.
78+
if (rf !== RenderFlags.Create) {
79+
const creationMode = getCreationMode();
80+
const checkNoChangesMode = getCheckNoChangesMode();
81+
setHostBindings(tView, viewData);
82+
83+
if (!checkNoChangesMode) {
84+
executeInitHooks(viewData, tView, creationMode);
85+
}
86+
refreshDynamicEmbeddedViews(viewData);
8287

83-
// Content query results must be refreshed before content hooks are called.
84-
refreshContentQueries(tView);
88+
// Content query results must be refreshed before content hooks are called.
89+
refreshContentQueries(tView);
8590

86-
if (!checkNoChangesMode) {
87-
executeHooks(viewData, tView.contentHooks, tView.contentCheckHooks, creationMode);
91+
if (!checkNoChangesMode) {
92+
executeHooks(viewData, tView.contentHooks, tView.contentCheckHooks, creationMode);
93+
}
8894
}
8995

90-
refreshChildComponents(tView.components, parentFirstTemplatePass);
96+
refreshChildComponents(tView.components, parentFirstTemplatePass, rf);
9197
}
9298

9399

@@ -144,23 +150,14 @@ function refreshContentQueries(tView: TView): void {
144150

145151
/** Refreshes child components in the current view. */
146152
function refreshChildComponents(
147-
components: number[] | null, parentFirstTemplatePass: boolean): void {
153+
components: number[] | null, parentFirstTemplatePass: boolean, rf: RenderFlags | null): void {
148154
if (components != null) {
149155
for (let i = 0; i < components.length; i++) {
150-
componentRefresh(components[i], parentFirstTemplatePass);
156+
componentRefresh(components[i], parentFirstTemplatePass, rf);
151157
}
152158
}
153159
}
154160

155-
export function executeInitAndContentHooks(viewData: LViewData): void {
156-
if (!getCheckNoChangesMode()) {
157-
const tView = getTView();
158-
const creationMode = getCreationMode();
159-
executeInitHooks(viewData, tView, creationMode);
160-
executeHooks(viewData, tView.contentHooks, tView.contentCheckHooks, creationMode);
161-
}
162-
}
163-
164161
export function createLViewData<T>(
165162
renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags,
166163
sanitizer?: Sanitizer | null): LViewData {
@@ -304,7 +301,7 @@ export function renderTemplate<T>(
304301
createLViewData(renderer, componentTView, context, LViewFlags.CheckAlways, sanitizer);
305302
hostView[HOST_NODE] = createNodeAtIndex(0, TNodeType.Element, hostNode, null, null);
306303
}
307-
renderComponentOrTemplate(hostView, context, templateFn);
304+
renderComponentOrTemplate(hostView, context, null, templateFn);
308305

309306
return hostView;
310307
}
@@ -369,7 +366,7 @@ export function renderEmbeddedTemplate<T>(
369366
namespaceHTML();
370367
tView.template !(rf, context);
371368
if (rf & RenderFlags.Update) {
372-
refreshDescendantViews(viewToRender);
369+
refreshDescendantViews(viewToRender, null);
373370
} else {
374371
// This must be set to false immediately after the first creation run because in an
375372
// 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 {
404401
}
405402

406403
function renderComponentOrTemplate<T>(
407-
hostView: LViewData, componentOrContext: T, templateFn?: ComponentTemplate<T>) {
404+
hostView: LViewData, componentOrContext: T, rf: RenderFlags | null,
405+
templateFn?: ComponentTemplate<T>) {
408406
const rendererFactory = getRendererFactory();
409407
const oldView = enterView(hostView, hostView[HOST_NODE]);
410408
try {
@@ -413,17 +411,9 @@ function renderComponentOrTemplate<T>(
413411
}
414412
if (templateFn) {
415413
namespaceHTML();
416-
templateFn(getRenderFlags(hostView), componentOrContext !);
417-
refreshDescendantViews(hostView);
418-
} else {
419-
executeInitAndContentHooks(hostView);
420-
421-
// Element was stored at 0 in data and directive was stored at 0 in directives
422-
// in renderComponent()
423-
setHostBindings(getTView(), hostView);
424-
refreshDynamicEmbeddedViews(hostView);
425-
componentRefresh(HEADER_OFFSET, false);
414+
templateFn(rf || getRenderFlags(hostView), componentOrContext !);
426415
}
416+
refreshDescendantViews(hostView, rf);
427417
} finally {
428418
if (rendererFactory.end) {
429419
rendererFactory.end();
@@ -1488,7 +1478,7 @@ function findDirectiveMatches(tView: TView, viewData: LViewData, tNode: TNode):
14881478
}
14891479

14901480
/** Stores index of component's host element so it will be queued for view refresh during CD. */
1491-
function queueComponentIndexForCheck(previousOrParentTNode: TNode): void {
1481+
export function queueComponentIndexForCheck(previousOrParentTNode: TNode): void {
14921482
ngDevMode &&
14931483
assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.');
14941484
const tView = getTView();
@@ -1829,7 +1819,7 @@ export function containerRefreshEnd(): void {
18291819
* Goes over dynamic embedded views (ones created through ViewContainerRef APIs) and refreshes them
18301820
* by executing an associated template function.
18311821
*/
1832-
export function refreshDynamicEmbeddedViews(lViewData: LViewData) {
1822+
function refreshDynamicEmbeddedViews(lViewData: LViewData) {
18331823
for (let current = getLViewChild(lViewData); current !== null; current = current[NEXT]) {
18341824
// Note: current can be an LViewData or an LContainer instance, but here we are only interested
18351825
// in LContainer. We can tell it's an LContainer because its length is less than the LViewData
@@ -1957,7 +1947,7 @@ function getOrCreateEmbeddedTView(
19571947
export function embeddedViewEnd(): void {
19581948
const viewData = getViewData();
19591949
const viewHost = viewData[HOST_NODE];
1960-
refreshDescendantViews(viewData);
1950+
refreshDescendantViews(viewData, null);
19611951
leaveView(viewData[PARENT] !);
19621952
setPreviousOrParentTNode(viewHost !);
19631953
setIsParent(false);
@@ -1971,15 +1961,15 @@ export function embeddedViewEnd(): void {
19711961
* @param adjustedElementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET)
19721962
*/
19731963
export function componentRefresh<T>(
1974-
adjustedElementIndex: number, parentFirstTemplatePass: boolean): void {
1964+
adjustedElementIndex: number, parentFirstTemplatePass: boolean, rf: RenderFlags | null): void {
19751965
ngDevMode && assertDataInRange(adjustedElementIndex);
19761966
const hostView = getComponentViewByIndex(adjustedElementIndex, getViewData());
19771967
ngDevMode && assertNodeType(getTView().data[adjustedElementIndex] as TNode, TNodeType.Element);
19781968

19791969
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
19801970
if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
19811971
parentFirstTemplatePass && syncViewWithBlueprint(hostView);
1982-
detectChangesInternal(hostView, hostView[CONTEXT]);
1972+
detectChangesInternal(hostView, hostView[CONTEXT], rf);
19831973
}
19841974
}
19851975

@@ -2261,7 +2251,8 @@ export function tick<T>(component: T): void {
22612251
function tickRootContext(rootContext: RootContext) {
22622252
for (let i = 0; i < rootContext.components.length; i++) {
22632253
const rootComponent = rootContext.components[i];
2264-
renderComponentOrTemplate(readPatchedLViewData(rootComponent) !, rootComponent);
2254+
renderComponentOrTemplate(
2255+
readPatchedLViewData(rootComponent) !, rootComponent, RenderFlags.Update);
22652256
}
22662257
}
22672258

@@ -2279,7 +2270,7 @@ function tickRootContext(rootContext: RootContext) {
22792270
* @param component The component which the change detection should be performed on.
22802271
*/
22812272
export function detectChanges<T>(component: T): void {
2282-
detectChangesInternal(getComponentViewByInstance(component) !, component);
2273+
detectChangesInternal(getComponentViewByInstance(component) !, component, null);
22832274
}
22842275

22852276
/**
@@ -2326,32 +2317,35 @@ export function checkNoChangesInRootView(lViewData: LViewData): void {
23262317
}
23272318

23282319
/** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */
2329-
export function detectChangesInternal<T>(hostView: LViewData, component: T) {
2320+
function detectChangesInternal<T>(hostView: LViewData, component: T, rf: RenderFlags | null) {
23302321
const hostTView = hostView[TVIEW];
23312322
const oldView = enterView(hostView, hostView[HOST_NODE]);
23322323
const templateFn = hostTView.template !;
23332324
const viewQuery = hostTView.viewQuery;
23342325

23352326
try {
23362327
namespaceHTML();
2337-
createViewQuery(viewQuery, hostView[FLAGS], component);
2338-
templateFn(getRenderFlags(hostView), component);
2339-
refreshDescendantViews(hostView);
2340-
updateViewQuery(viewQuery, component);
2328+
createViewQuery(viewQuery, rf, hostView[FLAGS], component);
2329+
templateFn(rf || getRenderFlags(hostView), component);
2330+
refreshDescendantViews(hostView, rf);
2331+
updateViewQuery(viewQuery, hostView[FLAGS], component);
23412332
} finally {
2342-
leaveView(oldView);
2333+
leaveView(oldView, rf === RenderFlags.Create);
23432334
}
23442335
}
23452336

23462337
function createViewQuery<T>(
2347-
viewQuery: ComponentQuery<{}>| null, flags: LViewFlags, component: T): void {
2348-
if (viewQuery && (flags & LViewFlags.CreationMode)) {
2338+
viewQuery: ComponentQuery<{}>| null, renderFlags: RenderFlags | null, viewFlags: LViewFlags,
2339+
component: T): void {
2340+
if (viewQuery && (renderFlags === RenderFlags.Create ||
2341+
(renderFlags === null && (viewFlags & LViewFlags.CreationMode)))) {
23492342
viewQuery(RenderFlags.Create, component);
23502343
}
23512344
}
23522345

2353-
function updateViewQuery<T>(viewQuery: ComponentQuery<{}>| null, component: T): void {
2354-
if (viewQuery) {
2346+
function updateViewQuery<T>(
2347+
viewQuery: ComponentQuery<{}>| null, flags: LViewFlags, component: T): void {
2348+
if (viewQuery && flags & RenderFlags.Update) {
23552349
viewQuery(RenderFlags.Update, component);
23562350
}
23572351
}

packages/core/test/bundling/animation_world/bundle.golden_symbols.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,9 +500,6 @@
500500
{
501501
"name": "executeHooks"
502502
},
503-
{
504-
"name": "executeInitAndContentHooks"
505-
},
506503
{
507504
"name": "executeInitHooks"
508505
},

packages/core/test/bundling/hello_world/bundle.golden_symbols.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,6 @@
200200
{
201201
"name": "executeHooks"
202202
},
203-
{
204-
"name": "executeInitAndContentHooks"
205-
},
206203
{
207204
"name": "executeInitHooks"
208205
},
@@ -371,6 +368,9 @@
371368
{
372369
"name": "prefillHostVars"
373370
},
371+
{
372+
"name": "queueComponentIndexForCheck"
373+
},
374374
{
375375
"name": "queueHostBindingForCheck"
376376
},

packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3194,9 +3194,6 @@
31943194
{
31953195
"name": "executeHooks"
31963196
},
3197-
{
3198-
"name": "executeInitAndContentHooks"
3199-
},
32003197
{
32013198
"name": "executeInitHooks"
32023199
},
@@ -4145,6 +4142,9 @@
41454142
{
41464143
"name": "queryDef"
41474144
},
4145+
{
4146+
"name": "queueComponentIndexForCheck"
4147+
},
41484148
{
41494149
"name": "queueContentHooks"
41504150
},

packages/core/test/bundling/todo/bundle.golden_symbols.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -554,9 +554,6 @@
554554
{
555555
"name": "executeHooks"
556556
},
557-
{
558-
"name": "executeInitAndContentHooks"
559-
},
560557
{
561558
"name": "executeInitHooks"
562559
},

packages/core/test/bundling/todo_r2/bundle.golden_symbols.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,9 +1505,6 @@
15051505
{
15061506
"name": "executeHooks"
15071507
},
1508-
{
1509-
"name": "executeInitAndContentHooks"
1510-
},
15111508
{
15121509
"name": "executeInitHooks"
15131510
},

0 commit comments

Comments
 (0)