Skip to content

Commit 2d53bac

Browse files
pkozlowski-opensourcealxhub
authored andcommitted
refactor(core): reuse existing logic in ComponentRef impl (#59806)
This change removes some code and logic duplication by re-using the existing functionality. It also pulls some code into separate methods for clarity. PR Close #59806
1 parent 4684e67 commit 2d53bac

File tree

12 files changed

+117
-103
lines changed

12 files changed

+117
-103
lines changed

packages/core/src/render3/component_ref.ts

Lines changed: 106 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import {
1616
import {Injector} from '../di/injector';
1717
import {EnvironmentInjector} from '../di/r3_injector';
1818
import {RuntimeError, RuntimeErrorCode} from '../errors';
19-
import {DehydratedView} from '../hydration/interfaces';
20-
import {retrieveHydrationInfo} from '../hydration/utils';
2119
import {Type} from '../interface/type';
2220
import {
2321
ComponentFactory as AbstractComponentFactory,
@@ -28,7 +26,6 @@ import {createElementRef, ElementRef} from '../linker/element_ref';
2826
import {NgModuleRef} from '../linker/ng_module_factory';
2927
import {RendererFactory2} from '../render/api';
3028
import {Sanitizer} from '../sanitization/sanitizer';
31-
import {assertDefined} from '../util/assert';
3229

3330
import {assertComponentType} from './assert';
3431
import {attachPatchData} from './context_discovery';
@@ -43,6 +40,7 @@ import {
4340
createDirectivesInstances,
4441
createLView,
4542
createTView,
43+
getInitialLViewFlagsFromDef,
4644
initializeDirectives,
4745
locateHostElement,
4846
resolveHostDirectives,
@@ -58,7 +56,7 @@ import {
5856
TNode,
5957
TNodeType,
6058
} from './interfaces/node';
61-
import {RNode} from './interfaces/renderer_dom';
59+
import {RElement, RNode} from './interfaces/renderer_dom';
6260
import {
6361
CONTEXT,
6462
HEADER_OFFSET,
@@ -70,17 +68,19 @@ import {
7068
} from './interfaces/view';
7169
import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces';
7270

71+
import {retrieveHydrationInfo} from '../hydration/utils';
7372
import {ChainedInjector} from './chained_injector';
7473
import {createElementNode, setupStaticAttributes} from './dom_node_manipulation';
7574
import {unregisterLView} from './interfaces/lview_tracking';
75+
import {Renderer} from './interfaces/renderer';
7676
import {
7777
extractAttrsAndClassesFromSelector,
7878
stringifyCSSSelectorList,
7979
} from './node_selector_matcher';
8080
import {profiler} from './profiler';
8181
import {ProfilerEvent} from './profiler_types';
8282
import {executeContentQueries} from './queries/query_execution';
83-
import {enterView, getCurrentTNode, getLView, leaveView} from './state';
83+
import {enterView, leaveView} from './state';
8484
import {computeStaticStyling} from './styling/static_styling';
8585
import {getOrCreateTNode} from './tnode_manipulation';
8686
import {mergeHostAttrs} from './util/attrs_utils';
@@ -150,9 +150,74 @@ function toRefArray<
150150
return array;
151151
}
152152

153-
function getNamespace(elementName: string): string | null {
154-
const name = elementName.toLowerCase();
155-
return name === 'svg' ? SVG_NAMESPACE : name === 'math' ? MATH_ML_NAMESPACE : null;
153+
function verifyNotAnOrphanComponent(componentDef: ComponentDef<unknown>) {
154+
// TODO(pk): create assert that verifies ngDevMode
155+
if (
156+
(typeof ngJitMode === 'undefined' || ngJitMode) &&
157+
componentDef.debugInfo?.forbidOrphanRendering
158+
) {
159+
if (depsTracker.isOrphanComponent(componentDef.type)) {
160+
throw new RuntimeError(
161+
RuntimeErrorCode.RUNTIME_DEPS_ORPHAN_COMPONENT,
162+
`Orphan component found! Trying to render the component ${debugStringifyTypeForError(
163+
componentDef.type,
164+
)} without first loading the NgModule that declares it. It is recommended to make this component standalone in order to avoid this error. If this is not possible now, import the component's NgModule in the appropriate NgModule, or the standalone component in which you are trying to render this component. If this is a lazy import, load the NgModule lazily as well and use its module injector.`,
165+
);
166+
}
167+
}
168+
}
169+
170+
function createRootViewInjector(
171+
componentDef: ComponentDef<unknown>,
172+
environmentInjector: EnvironmentInjector | NgModuleRef<any> | undefined,
173+
injector: Injector,
174+
): Injector {
175+
let realEnvironmentInjector =
176+
environmentInjector instanceof EnvironmentInjector
177+
? environmentInjector
178+
: environmentInjector?.injector;
179+
180+
if (realEnvironmentInjector && componentDef.getStandaloneInjector !== null) {
181+
realEnvironmentInjector =
182+
componentDef.getStandaloneInjector(realEnvironmentInjector) || realEnvironmentInjector;
183+
}
184+
185+
const rootViewInjector = realEnvironmentInjector
186+
? new ChainedInjector(injector, realEnvironmentInjector)
187+
: injector;
188+
return rootViewInjector;
189+
}
190+
191+
function createRootLViewEnvironment(rootLViewInjector: Injector): LViewEnvironment {
192+
const rendererFactory = rootLViewInjector.get(RendererFactory2, null);
193+
if (rendererFactory === null) {
194+
throw new RuntimeError(
195+
RuntimeErrorCode.RENDERER_NOT_FOUND,
196+
ngDevMode &&
197+
'Angular was not able to inject a renderer (RendererFactory2). ' +
198+
'Likely this is due to a broken DI hierarchy. ' +
199+
'Make sure that any injector used to create this component has a correct parent.',
200+
);
201+
}
202+
203+
const sanitizer = rootLViewInjector.get(Sanitizer, null);
204+
const changeDetectionScheduler = rootLViewInjector.get(ChangeDetectionScheduler, null);
205+
206+
return {
207+
rendererFactory,
208+
sanitizer,
209+
changeDetectionScheduler,
210+
};
211+
}
212+
213+
function createHostElement(componentDef: ComponentDef<unknown>, render: Renderer): RElement {
214+
// Determine a tag name used for creating host elements when this component is created
215+
// dynamically. Default to 'div' if this component did not specify any tag name in its
216+
// selector.
217+
const tagName = ((componentDef.selectors[0][0] as string) || 'div').toLowerCase();
218+
const namespace =
219+
tagName === 'svg' ? SVG_NAMESPACE : tagName === 'math' ? MATH_ML_NAMESPACE : null;
220+
return createElementNode(render, tagName, namespace);
156221
}
157222

158223
/**
@@ -214,84 +279,8 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
214279

215280
const prevConsumer = setActiveConsumer(null);
216281
try {
217-
// Check if the component is orphan
218-
if (
219-
ngDevMode &&
220-
(typeof ngJitMode === 'undefined' || ngJitMode) &&
221-
this.componentDef.debugInfo?.forbidOrphanRendering
222-
) {
223-
if (depsTracker.isOrphanComponent(this.componentType)) {
224-
throw new RuntimeError(
225-
RuntimeErrorCode.RUNTIME_DEPS_ORPHAN_COMPONENT,
226-
`Orphan component found! Trying to render the component ${debugStringifyTypeForError(
227-
this.componentType,
228-
)} without first loading the NgModule that declares it. It is recommended to make this component standalone in order to avoid this error. If this is not possible now, import the component's NgModule in the appropriate NgModule, or the standalone component in which you are trying to render this component. If this is a lazy import, load the NgModule lazily as well and use its module injector.`,
229-
);
230-
}
231-
}
232-
233-
environmentInjector = environmentInjector || this.ngModule;
234-
235-
let realEnvironmentInjector =
236-
environmentInjector instanceof EnvironmentInjector
237-
? environmentInjector
238-
: environmentInjector?.injector;
239-
240-
if (realEnvironmentInjector && this.componentDef.getStandaloneInjector !== null) {
241-
realEnvironmentInjector =
242-
this.componentDef.getStandaloneInjector(realEnvironmentInjector) ||
243-
realEnvironmentInjector;
244-
}
245-
246-
const rootViewInjector = realEnvironmentInjector
247-
? new ChainedInjector(injector, realEnvironmentInjector)
248-
: injector;
249-
250-
const rendererFactory = rootViewInjector.get(RendererFactory2, null);
251-
if (rendererFactory === null) {
252-
throw new RuntimeError(
253-
RuntimeErrorCode.RENDERER_NOT_FOUND,
254-
ngDevMode &&
255-
'Angular was not able to inject a renderer (RendererFactory2). ' +
256-
'Likely this is due to a broken DI hierarchy. ' +
257-
'Make sure that any injector used to create this component has a correct parent.',
258-
);
259-
}
260-
const sanitizer = rootViewInjector.get(Sanitizer, null);
261-
262-
const changeDetectionScheduler = rootViewInjector.get(ChangeDetectionScheduler, null);
263-
264-
const environment: LViewEnvironment = {
265-
rendererFactory,
266-
sanitizer,
267-
changeDetectionScheduler,
268-
};
269-
270-
const hostRenderer = rendererFactory.createRenderer(null, this.componentDef);
271-
// Determine a tag name used for creating host elements when this component is created
272-
// dynamically. Default to 'div' if this component did not specify any tag name in its
273-
// selector.
274-
const elementName = (this.componentDef.selectors[0][0] as string) || 'div';
275-
const hostRNode = rootSelectorOrNode
276-
? locateHostElement(
277-
hostRenderer,
278-
rootSelectorOrNode,
279-
this.componentDef.encapsulation,
280-
rootViewInjector,
281-
)
282-
: createElementNode(hostRenderer, elementName, getNamespace(elementName));
283-
284-
let rootFlags = LViewFlags.IsRoot;
285-
if (this.componentDef.signals) {
286-
rootFlags |= LViewFlags.SignalView;
287-
} else if (!this.componentDef.onPush) {
288-
rootFlags |= LViewFlags.CheckAlways;
289-
}
290-
291-
let hydrationInfo: DehydratedView | null = null;
292-
if (hostRNode !== null) {
293-
hydrationInfo = retrieveHydrationInfo(hostRNode, rootViewInjector, true /* isRootView */);
294-
}
282+
const cmpDef = this.componentDef;
283+
ngDevMode && verifyNotAnOrphanComponent(cmpDef);
295284

296285
// Create the root view. Uses empty TView and ContentTemplate.
297286
const rootTView = createTView(
@@ -307,21 +296,39 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
307296
null,
308297
null,
309298
);
299+
300+
const rootViewInjector = createRootViewInjector(
301+
cmpDef,
302+
environmentInjector || this.ngModule,
303+
injector,
304+
);
305+
306+
const environment = createRootLViewEnvironment(rootViewInjector);
307+
const hostRenderer = environment.rendererFactory.createRenderer(null, cmpDef);
308+
const hostElement = rootSelectorOrNode
309+
? locateHostElement(
310+
hostRenderer,
311+
rootSelectorOrNode,
312+
cmpDef.encapsulation,
313+
rootViewInjector,
314+
)
315+
: createHostElement(cmpDef, hostRenderer);
316+
310317
const rootLView = createLView<T>(
311318
null,
312319
rootTView,
313320
null,
314-
rootFlags,
321+
LViewFlags.IsRoot | getInitialLViewFlagsFromDef(cmpDef),
315322
null,
316323
null,
317324
environment,
318325
hostRenderer,
319326
rootViewInjector,
320327
null,
321-
hydrationInfo,
328+
retrieveHydrationInfo(hostElement, rootViewInjector, true /* isRootView */),
322329
);
323330

324-
rootLView[HEADER_OFFSET] = hostRNode;
331+
rootLView[HEADER_OFFSET] = hostElement;
325332

326333
// rootView is the parent when bootstrapping
327334
// TODO(misko): it looks like we are entering view here but we don't really need to as
@@ -337,9 +344,9 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
337344
const tAttributes = rootSelectorOrNode
338345
? ['ng-version', '0.0.0-PLACEHOLDER']
339346
: // Extract attributes and classes from the first selector only to match VE behavior.
340-
extractAttrsAndClassesFromSelector(this.componentDef.selectors[0]);
347+
extractAttrsAndClassesFromSelector(cmpDef.selectors[0]);
341348

342-
// TODO: this logic is shared with the element instruction first create pass
349+
// TODO: this logic is shared with the element instruction first create pass - minus directive matching
343350
const hostTNode = getOrCreateTNode(
344351
rootTView,
345352
HEADER_OFFSET,
@@ -349,27 +356,19 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
349356
);
350357

351358
const [directiveDefs, hostDirectiveDefs] = resolveHostDirectives(rootTView, hostTNode, [
352-
this.componentDef,
359+
cmpDef,
353360
]);
354361
initializeDirectives(rootTView, rootLView, hostTNode, directiveDefs, {}, hostDirectiveDefs);
355-
356-
for (const def of directiveDefs) {
357-
hostTNode.mergedAttrs = mergeHostAttrs(hostTNode.mergedAttrs, def.hostAttrs);
358-
}
359362
hostTNode.mergedAttrs = mergeHostAttrs(hostTNode.mergedAttrs, tAttributes);
360363

361364
computeStaticStyling(hostTNode, hostTNode.mergedAttrs, true);
362365

363366
// TODO(crisbeto): in practice `hostRNode` should always be defined, but there are some
364367
// tests where the renderer is mocked out and `undefined` is returned. We should update the
365368
// tests so that this check can be removed.
366-
if (hostRNode) {
367-
setupStaticAttributes(hostRenderer, hostRNode, hostTNode);
368-
attachPatchData(hostRNode, rootLView);
369-
}
370-
371-
if (projectableNodes !== undefined) {
372-
projectNodes(hostTNode, this.ngContentSelectors, projectableNodes);
369+
if (hostElement) {
370+
setupStaticAttributes(hostRenderer, hostElement, hostTNode);
371+
attachPatchData(hostElement, rootLView);
373372
}
374373

375374
// TODO(pk): this logic is similar to the instruction code where a node can have directives
@@ -379,6 +378,10 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
379378
// TODO(pk): code / logic duplication with the elementEnd and similar instructions
380379
registerPostOrderHooks(rootTView, hostTNode);
381380

381+
if (projectableNodes !== undefined) {
382+
projectNodes(hostTNode, this.ngContentSelectors, projectableNodes);
383+
}
384+
382385
componentView = getComponentLViewByIndex(hostTNode.index, rootLView);
383386

384387
// TODO(pk): why do we need this logic?

packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@
306306
"getDirectiveDef",
307307
"getFactoryDef",
308308
"getFirstLContainer",
309+
"getInitialLViewFlagsFromDef",
309310
"getInjectImplementation",
310311
"getInjectableDef",
311312
"getInjectorDef",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@
327327
"getDirectiveDef",
328328
"getFactoryDef",
329329
"getFirstLContainer",
330+
"getInitialLViewFlagsFromDef",
330331
"getInjectImplementation",
331332
"getInjectableDef",
332333
"getInjectorDef",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@
255255
"getDirectiveDef",
256256
"getFactoryDef",
257257
"getFirstLContainer",
258+
"getInitialLViewFlagsFromDef",
258259
"getInjectImplementation",
259260
"getInjectableDef",
260261
"getInjectorDef",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@
309309
"getFactoryDef",
310310
"getFirstLContainer",
311311
"getFirstNativeNode",
312+
"getInitialLViewFlagsFromDef",
312313
"getInjectImplementation",
313314
"getInjectableDef",
314315
"getInjectorDef",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@
372372
"getFactoryOf",
373373
"getFirstLContainer",
374374
"getFirstNativeNode",
375+
"getInitialLViewFlagsFromDef",
375376
"getInjectImplementation",
376377
"getInjectableDef",
377378
"getInjectorDef",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@
358358
"getFactoryOf",
359359
"getFirstLContainer",
360360
"getFirstNativeNode",
361+
"getInitialLViewFlagsFromDef",
361362
"getInjectImplementation",
362363
"getInjectableDef",
363364
"getInjectorDef",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@
198198
"getDeclarationTNode",
199199
"getFactoryDef",
200200
"getFirstLContainer",
201+
"getInitialLViewFlagsFromDef",
201202
"getInjectImplementation",
202203
"getInjectableDef",
203204
"getInjectorDef",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@
263263
"getFactoryDef",
264264
"getFilteredHeaders",
265265
"getFirstLContainer",
266+
"getInitialLViewFlagsFromDef",
266267
"getInjectImplementation",
267268
"getInjectableDef",
268269
"getInjectorDef",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@
443443
"getFirstNativeNode",
444444
"getIdxOfMatchingSelector",
445445
"getInherited",
446+
"getInitialLViewFlagsFromDef",
446447
"getInjectImplementation",
447448
"getInjectableDef",
448449
"getInjectorDef",

0 commit comments

Comments
 (0)