Skip to content

Commit 2a0731f

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 419634c commit 2a0731f

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,16 +68,18 @@ 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

8181
import {executeContentQueries} from './queries/query_execution';
82-
import {enterView, getCurrentTNode, getLView, leaveView} from './state';
82+
import {enterView, leaveView} from './state';
8383
import {computeStaticStyling} from './styling/static_styling';
8484
import {getOrCreateTNode} from './tnode_manipulation';
8585
import {mergeHostAttrs} from './util/attrs_utils';
@@ -149,9 +149,74 @@ function toRefArray<
149149
return array;
150150
}
151151

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

157222
/**
@@ -211,84 +276,8 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
211276
): AbstractComponentRef<T> {
212277
const prevConsumer = setActiveConsumer(null);
213278
try {
214-
// Check if the component is orphan
215-
if (
216-
ngDevMode &&
217-
(typeof ngJitMode === 'undefined' || ngJitMode) &&
218-
this.componentDef.debugInfo?.forbidOrphanRendering
219-
) {
220-
if (depsTracker.isOrphanComponent(this.componentType)) {
221-
throw new RuntimeError(
222-
RuntimeErrorCode.RUNTIME_DEPS_ORPHAN_COMPONENT,
223-
`Orphan component found! Trying to render the component ${debugStringifyTypeForError(
224-
this.componentType,
225-
)} 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.`,
226-
);
227-
}
228-
}
229-
230-
environmentInjector = environmentInjector || this.ngModule;
231-
232-
let realEnvironmentInjector =
233-
environmentInjector instanceof EnvironmentInjector
234-
? environmentInjector
235-
: environmentInjector?.injector;
236-
237-
if (realEnvironmentInjector && this.componentDef.getStandaloneInjector !== null) {
238-
realEnvironmentInjector =
239-
this.componentDef.getStandaloneInjector(realEnvironmentInjector) ||
240-
realEnvironmentInjector;
241-
}
242-
243-
const rootViewInjector = realEnvironmentInjector
244-
? new ChainedInjector(injector, realEnvironmentInjector)
245-
: injector;
246-
247-
const rendererFactory = rootViewInjector.get(RendererFactory2, null);
248-
if (rendererFactory === null) {
249-
throw new RuntimeError(
250-
RuntimeErrorCode.RENDERER_NOT_FOUND,
251-
ngDevMode &&
252-
'Angular was not able to inject a renderer (RendererFactory2). ' +
253-
'Likely this is due to a broken DI hierarchy. ' +
254-
'Make sure that any injector used to create this component has a correct parent.',
255-
);
256-
}
257-
const sanitizer = rootViewInjector.get(Sanitizer, null);
258-
259-
const changeDetectionScheduler = rootViewInjector.get(ChangeDetectionScheduler, null);
260-
261-
const environment: LViewEnvironment = {
262-
rendererFactory,
263-
sanitizer,
264-
changeDetectionScheduler,
265-
};
266-
267-
const hostRenderer = rendererFactory.createRenderer(null, this.componentDef);
268-
// Determine a tag name used for creating host elements when this component is created
269-
// dynamically. Default to 'div' if this component did not specify any tag name in its
270-
// selector.
271-
const elementName = (this.componentDef.selectors[0][0] as string) || 'div';
272-
const hostRNode = rootSelectorOrNode
273-
? locateHostElement(
274-
hostRenderer,
275-
rootSelectorOrNode,
276-
this.componentDef.encapsulation,
277-
rootViewInjector,
278-
)
279-
: createElementNode(hostRenderer, elementName, getNamespace(elementName));
280-
281-
let rootFlags = LViewFlags.IsRoot;
282-
if (this.componentDef.signals) {
283-
rootFlags |= LViewFlags.SignalView;
284-
} else if (!this.componentDef.onPush) {
285-
rootFlags |= LViewFlags.CheckAlways;
286-
}
287-
288-
let hydrationInfo: DehydratedView | null = null;
289-
if (hostRNode !== null) {
290-
hydrationInfo = retrieveHydrationInfo(hostRNode, rootViewInjector, true /* isRootView */);
291-
}
279+
const cmpDef = this.componentDef;
280+
ngDevMode && verifyNotAnOrphanComponent(cmpDef);
292281

293282
// Create the root view. Uses empty TView and ContentTemplate.
294283
const rootTView = createTView(
@@ -304,21 +293,39 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
304293
null,
305294
null,
306295
);
296+
297+
const rootViewInjector = createRootViewInjector(
298+
cmpDef,
299+
environmentInjector || this.ngModule,
300+
injector,
301+
);
302+
303+
const environment = createRootLViewEnvironment(rootViewInjector);
304+
const hostRenderer = environment.rendererFactory.createRenderer(null, cmpDef);
305+
const hostElement = rootSelectorOrNode
306+
? locateHostElement(
307+
hostRenderer,
308+
rootSelectorOrNode,
309+
cmpDef.encapsulation,
310+
rootViewInjector,
311+
)
312+
: createHostElement(cmpDef, hostRenderer);
313+
307314
const rootLView = createLView<T>(
308315
null,
309316
rootTView,
310317
null,
311-
rootFlags,
318+
LViewFlags.IsRoot | getInitialLViewFlagsFromDef(cmpDef),
312319
null,
313320
null,
314321
environment,
315322
hostRenderer,
316323
rootViewInjector,
317324
null,
318-
hydrationInfo,
325+
retrieveHydrationInfo(hostElement, rootViewInjector, true /* isRootView */),
319326
);
320327

321-
rootLView[HEADER_OFFSET] = hostRNode;
328+
rootLView[HEADER_OFFSET] = hostElement;
322329

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

339-
// TODO: this logic is shared with the element instruction first create pass
346+
// TODO: this logic is shared with the element instruction first create pass - minus directive matching
340347
const hostTNode = getOrCreateTNode(
341348
rootTView,
342349
HEADER_OFFSET,
@@ -346,27 +353,19 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
346353
);
347354

348355
const [directiveDefs, hostDirectiveDefs] = resolveHostDirectives(rootTView, hostTNode, [
349-
this.componentDef,
356+
cmpDef,
350357
]);
351358
initializeDirectives(rootTView, rootLView, hostTNode, directiveDefs, {}, hostDirectiveDefs);
352-
353-
for (const def of directiveDefs) {
354-
hostTNode.mergedAttrs = mergeHostAttrs(hostTNode.mergedAttrs, def.hostAttrs);
355-
}
356359
hostTNode.mergedAttrs = mergeHostAttrs(hostTNode.mergedAttrs, tAttributes);
357360

358361
computeStaticStyling(hostTNode, hostTNode.mergedAttrs, true);
359362

360363
// TODO(crisbeto): in practice `hostRNode` should always be defined, but there are some
361364
// tests where the renderer is mocked out and `undefined` is returned. We should update the
362365
// tests so that this check can be removed.
363-
if (hostRNode) {
364-
setupStaticAttributes(hostRenderer, hostRNode, hostTNode);
365-
attachPatchData(hostRNode, rootLView);
366-
}
367-
368-
if (projectableNodes !== undefined) {
369-
projectNodes(hostTNode, this.ngContentSelectors, projectableNodes);
366+
if (hostElement) {
367+
setupStaticAttributes(hostRenderer, hostElement, hostTNode);
368+
attachPatchData(hostElement, rootLView);
370369
}
371370

372371
// TODO(pk): this logic is similar to the instruction code where a node can have directives
@@ -376,6 +375,10 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
376375
// TODO(pk): code / logic duplication with the elementEnd and similar instructions
377376
registerPostOrderHooks(rootTView, hostTNode);
378377

378+
if (projectableNodes !== undefined) {
379+
projectNodes(hostTNode, this.ngContentSelectors, projectableNodes);
380+
}
381+
379382
componentView = getComponentLViewByIndex(hostTNode.index, rootLView);
380383

381384
// 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)