From 2a0731f9c7dff4e8c1cc7fa643feefa4a246c110 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Wed, 29 Jan 2025 18:10:58 +0100 Subject: [PATCH] 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 --- packages/core/src/render3/component_ref.ts | 209 +++++++++--------- .../bundle.golden_symbols.json | 1 + .../animations/bundle.golden_symbols.json | 1 + .../cyclic_import/bundle.golden_symbols.json | 1 + .../bundling/defer/bundle.golden_symbols.json | 1 + .../forms_reactive/bundle.golden_symbols.json | 1 + .../bundle.golden_symbols.json | 1 + .../hello_world/bundle.golden_symbols.json | 1 + .../hydration/bundle.golden_symbols.json | 1 + .../router/bundle.golden_symbols.json | 1 + .../bundle.golden_symbols.json | 1 + .../bundling/todo/bundle.golden_symbols.json | 1 + 12 files changed, 117 insertions(+), 103 deletions(-) diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index db84505a8cf..df3a5517ff8 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -16,8 +16,6 @@ import { import {Injector} from '../di/injector'; import {EnvironmentInjector} from '../di/r3_injector'; import {RuntimeError, RuntimeErrorCode} from '../errors'; -import {DehydratedView} from '../hydration/interfaces'; -import {retrieveHydrationInfo} from '../hydration/utils'; import {Type} from '../interface/type'; import { ComponentFactory as AbstractComponentFactory, @@ -28,7 +26,6 @@ import {createElementRef, ElementRef} from '../linker/element_ref'; import {NgModuleRef} from '../linker/ng_module_factory'; import {RendererFactory2} from '../render/api'; import {Sanitizer} from '../sanitization/sanitizer'; -import {assertDefined} from '../util/assert'; import {assertComponentType} from './assert'; import {attachPatchData} from './context_discovery'; @@ -43,6 +40,7 @@ import { createDirectivesInstances, createLView, createTView, + getInitialLViewFlagsFromDef, initializeDirectives, locateHostElement, resolveHostDirectives, @@ -58,7 +56,7 @@ import { TNode, TNodeType, } from './interfaces/node'; -import {RNode} from './interfaces/renderer_dom'; +import {RElement, RNode} from './interfaces/renderer_dom'; import { CONTEXT, HEADER_OFFSET, @@ -70,16 +68,18 @@ import { } from './interfaces/view'; import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces'; +import {retrieveHydrationInfo} from '../hydration/utils'; import {ChainedInjector} from './chained_injector'; import {createElementNode, setupStaticAttributes} from './dom_node_manipulation'; import {unregisterLView} from './interfaces/lview_tracking'; +import {Renderer} from './interfaces/renderer'; import { extractAttrsAndClassesFromSelector, stringifyCSSSelectorList, } from './node_selector_matcher'; import {executeContentQueries} from './queries/query_execution'; -import {enterView, getCurrentTNode, getLView, leaveView} from './state'; +import {enterView, leaveView} from './state'; import {computeStaticStyling} from './styling/static_styling'; import {getOrCreateTNode} from './tnode_manipulation'; import {mergeHostAttrs} from './util/attrs_utils'; @@ -149,9 +149,74 @@ function toRefArray< return array; } -function getNamespace(elementName: string): string | null { - const name = elementName.toLowerCase(); - return name === 'svg' ? SVG_NAMESPACE : name === 'math' ? MATH_ML_NAMESPACE : null; +function verifyNotAnOrphanComponent(componentDef: ComponentDef) { + // TODO(pk): create assert that verifies ngDevMode + if ( + (typeof ngJitMode === 'undefined' || ngJitMode) && + componentDef.debugInfo?.forbidOrphanRendering + ) { + if (depsTracker.isOrphanComponent(componentDef.type)) { + throw new RuntimeError( + RuntimeErrorCode.RUNTIME_DEPS_ORPHAN_COMPONENT, + `Orphan component found! Trying to render the component ${debugStringifyTypeForError( + componentDef.type, + )} 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.`, + ); + } + } +} + +function createRootViewInjector( + componentDef: ComponentDef, + environmentInjector: EnvironmentInjector | NgModuleRef | undefined, + injector: Injector, +): Injector { + let realEnvironmentInjector = + environmentInjector instanceof EnvironmentInjector + ? environmentInjector + : environmentInjector?.injector; + + if (realEnvironmentInjector && componentDef.getStandaloneInjector !== null) { + realEnvironmentInjector = + componentDef.getStandaloneInjector(realEnvironmentInjector) || realEnvironmentInjector; + } + + const rootViewInjector = realEnvironmentInjector + ? new ChainedInjector(injector, realEnvironmentInjector) + : injector; + return rootViewInjector; +} + +function createRootLViewEnvironment(rootLViewInjector: Injector): LViewEnvironment { + const rendererFactory = rootLViewInjector.get(RendererFactory2, null); + if (rendererFactory === null) { + throw new RuntimeError( + RuntimeErrorCode.RENDERER_NOT_FOUND, + ngDevMode && + 'Angular was not able to inject a renderer (RendererFactory2). ' + + 'Likely this is due to a broken DI hierarchy. ' + + 'Make sure that any injector used to create this component has a correct parent.', + ); + } + + const sanitizer = rootLViewInjector.get(Sanitizer, null); + const changeDetectionScheduler = rootLViewInjector.get(ChangeDetectionScheduler, null); + + return { + rendererFactory, + sanitizer, + changeDetectionScheduler, + }; +} + +function createHostElement(componentDef: ComponentDef, render: Renderer): RElement { + // Determine a tag name used for creating host elements when this component is created + // dynamically. Default to 'div' if this component did not specify any tag name in its + // selector. + const tagName = ((componentDef.selectors[0][0] as string) || 'div').toLowerCase(); + const namespace = + tagName === 'svg' ? SVG_NAMESPACE : tagName === 'math' ? MATH_ML_NAMESPACE : null; + return createElementNode(render, tagName, namespace); } /** @@ -211,84 +276,8 @@ export class ComponentFactory extends AbstractComponentFactory { ): AbstractComponentRef { const prevConsumer = setActiveConsumer(null); try { - // Check if the component is orphan - if ( - ngDevMode && - (typeof ngJitMode === 'undefined' || ngJitMode) && - this.componentDef.debugInfo?.forbidOrphanRendering - ) { - if (depsTracker.isOrphanComponent(this.componentType)) { - throw new RuntimeError( - RuntimeErrorCode.RUNTIME_DEPS_ORPHAN_COMPONENT, - `Orphan component found! Trying to render the component ${debugStringifyTypeForError( - this.componentType, - )} 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.`, - ); - } - } - - environmentInjector = environmentInjector || this.ngModule; - - let realEnvironmentInjector = - environmentInjector instanceof EnvironmentInjector - ? environmentInjector - : environmentInjector?.injector; - - if (realEnvironmentInjector && this.componentDef.getStandaloneInjector !== null) { - realEnvironmentInjector = - this.componentDef.getStandaloneInjector(realEnvironmentInjector) || - realEnvironmentInjector; - } - - const rootViewInjector = realEnvironmentInjector - ? new ChainedInjector(injector, realEnvironmentInjector) - : injector; - - const rendererFactory = rootViewInjector.get(RendererFactory2, null); - if (rendererFactory === null) { - throw new RuntimeError( - RuntimeErrorCode.RENDERER_NOT_FOUND, - ngDevMode && - 'Angular was not able to inject a renderer (RendererFactory2). ' + - 'Likely this is due to a broken DI hierarchy. ' + - 'Make sure that any injector used to create this component has a correct parent.', - ); - } - const sanitizer = rootViewInjector.get(Sanitizer, null); - - const changeDetectionScheduler = rootViewInjector.get(ChangeDetectionScheduler, null); - - const environment: LViewEnvironment = { - rendererFactory, - sanitizer, - changeDetectionScheduler, - }; - - const hostRenderer = rendererFactory.createRenderer(null, this.componentDef); - // Determine a tag name used for creating host elements when this component is created - // dynamically. Default to 'div' if this component did not specify any tag name in its - // selector. - const elementName = (this.componentDef.selectors[0][0] as string) || 'div'; - const hostRNode = rootSelectorOrNode - ? locateHostElement( - hostRenderer, - rootSelectorOrNode, - this.componentDef.encapsulation, - rootViewInjector, - ) - : createElementNode(hostRenderer, elementName, getNamespace(elementName)); - - let rootFlags = LViewFlags.IsRoot; - if (this.componentDef.signals) { - rootFlags |= LViewFlags.SignalView; - } else if (!this.componentDef.onPush) { - rootFlags |= LViewFlags.CheckAlways; - } - - let hydrationInfo: DehydratedView | null = null; - if (hostRNode !== null) { - hydrationInfo = retrieveHydrationInfo(hostRNode, rootViewInjector, true /* isRootView */); - } + const cmpDef = this.componentDef; + ngDevMode && verifyNotAnOrphanComponent(cmpDef); // Create the root view. Uses empty TView and ContentTemplate. const rootTView = createTView( @@ -304,21 +293,39 @@ export class ComponentFactory extends AbstractComponentFactory { null, null, ); + + const rootViewInjector = createRootViewInjector( + cmpDef, + environmentInjector || this.ngModule, + injector, + ); + + const environment = createRootLViewEnvironment(rootViewInjector); + const hostRenderer = environment.rendererFactory.createRenderer(null, cmpDef); + const hostElement = rootSelectorOrNode + ? locateHostElement( + hostRenderer, + rootSelectorOrNode, + cmpDef.encapsulation, + rootViewInjector, + ) + : createHostElement(cmpDef, hostRenderer); + const rootLView = createLView( null, rootTView, null, - rootFlags, + LViewFlags.IsRoot | getInitialLViewFlagsFromDef(cmpDef), null, null, environment, hostRenderer, rootViewInjector, null, - hydrationInfo, + retrieveHydrationInfo(hostElement, rootViewInjector, true /* isRootView */), ); - rootLView[HEADER_OFFSET] = hostRNode; + rootLView[HEADER_OFFSET] = hostElement; // rootView is the parent when bootstrapping // 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 extends AbstractComponentFactory { const tAttributes = rootSelectorOrNode ? ['ng-version', '0.0.0-PLACEHOLDER'] : // Extract attributes and classes from the first selector only to match VE behavior. - extractAttrsAndClassesFromSelector(this.componentDef.selectors[0]); + extractAttrsAndClassesFromSelector(cmpDef.selectors[0]); - // TODO: this logic is shared with the element instruction first create pass + // TODO: this logic is shared with the element instruction first create pass - minus directive matching const hostTNode = getOrCreateTNode( rootTView, HEADER_OFFSET, @@ -346,13 +353,9 @@ export class ComponentFactory extends AbstractComponentFactory { ); const [directiveDefs, hostDirectiveDefs] = resolveHostDirectives(rootTView, hostTNode, [ - this.componentDef, + cmpDef, ]); initializeDirectives(rootTView, rootLView, hostTNode, directiveDefs, {}, hostDirectiveDefs); - - for (const def of directiveDefs) { - hostTNode.mergedAttrs = mergeHostAttrs(hostTNode.mergedAttrs, def.hostAttrs); - } hostTNode.mergedAttrs = mergeHostAttrs(hostTNode.mergedAttrs, tAttributes); computeStaticStyling(hostTNode, hostTNode.mergedAttrs, true); @@ -360,13 +363,9 @@ export class ComponentFactory extends AbstractComponentFactory { // TODO(crisbeto): in practice `hostRNode` should always be defined, but there are some // tests where the renderer is mocked out and `undefined` is returned. We should update the // tests so that this check can be removed. - if (hostRNode) { - setupStaticAttributes(hostRenderer, hostRNode, hostTNode); - attachPatchData(hostRNode, rootLView); - } - - if (projectableNodes !== undefined) { - projectNodes(hostTNode, this.ngContentSelectors, projectableNodes); + if (hostElement) { + setupStaticAttributes(hostRenderer, hostElement, hostTNode); + attachPatchData(hostElement, rootLView); } // TODO(pk): this logic is similar to the instruction code where a node can have directives @@ -376,6 +375,10 @@ export class ComponentFactory extends AbstractComponentFactory { // TODO(pk): code / logic duplication with the elementEnd and similar instructions registerPostOrderHooks(rootTView, hostTNode); + if (projectableNodes !== undefined) { + projectNodes(hostTNode, this.ngContentSelectors, projectableNodes); + } + componentView = getComponentLViewByIndex(hostTNode.index, rootLView); // TODO(pk): why do we need this logic? diff --git a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json index df48d2dff85..474a638b2d9 100644 --- a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json @@ -306,6 +306,7 @@ "getDirectiveDef", "getFactoryDef", "getFirstLContainer", + "getInitialLViewFlagsFromDef", "getInjectImplementation", "getInjectableDef", "getInjectorDef", diff --git a/packages/core/test/bundling/animations/bundle.golden_symbols.json b/packages/core/test/bundling/animations/bundle.golden_symbols.json index 62af134a8e0..5415c8c5a06 100644 --- a/packages/core/test/bundling/animations/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations/bundle.golden_symbols.json @@ -327,6 +327,7 @@ "getDirectiveDef", "getFactoryDef", "getFirstLContainer", + "getInitialLViewFlagsFromDef", "getInjectImplementation", "getInjectableDef", "getInjectorDef", diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index c139251f2b0..14df7c69303 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -255,6 +255,7 @@ "getDirectiveDef", "getFactoryDef", "getFirstLContainer", + "getInitialLViewFlagsFromDef", "getInjectImplementation", "getInjectableDef", "getInjectorDef", diff --git a/packages/core/test/bundling/defer/bundle.golden_symbols.json b/packages/core/test/bundling/defer/bundle.golden_symbols.json index a66108aa3d8..d74d3c5306c 100644 --- a/packages/core/test/bundling/defer/bundle.golden_symbols.json +++ b/packages/core/test/bundling/defer/bundle.golden_symbols.json @@ -309,6 +309,7 @@ "getFactoryDef", "getFirstLContainer", "getFirstNativeNode", + "getInitialLViewFlagsFromDef", "getInjectImplementation", "getInjectableDef", "getInjectorDef", diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index 219fd338436..71a74ec5ee9 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -372,6 +372,7 @@ "getFactoryOf", "getFirstLContainer", "getFirstNativeNode", + "getInitialLViewFlagsFromDef", "getInjectImplementation", "getInjectableDef", "getInjectorDef", diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index 02d8aacd38d..871a10adddb 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -358,6 +358,7 @@ "getFactoryOf", "getFirstLContainer", "getFirstNativeNode", + "getInitialLViewFlagsFromDef", "getInjectImplementation", "getInjectableDef", "getInjectorDef", diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index a2987b942c9..81659430323 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -198,6 +198,7 @@ "getDeclarationTNode", "getFactoryDef", "getFirstLContainer", + "getInitialLViewFlagsFromDef", "getInjectImplementation", "getInjectableDef", "getInjectorDef", diff --git a/packages/core/test/bundling/hydration/bundle.golden_symbols.json b/packages/core/test/bundling/hydration/bundle.golden_symbols.json index 190a8726d8c..e6f35276b21 100644 --- a/packages/core/test/bundling/hydration/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hydration/bundle.golden_symbols.json @@ -263,6 +263,7 @@ "getFactoryDef", "getFilteredHeaders", "getFirstLContainer", + "getInitialLViewFlagsFromDef", "getInjectImplementation", "getInjectableDef", "getInjectorDef", diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 3ab9944b6c4..3aeaba62c12 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -443,6 +443,7 @@ "getFirstNativeNode", "getIdxOfMatchingSelector", "getInherited", + "getInitialLViewFlagsFromDef", "getInjectImplementation", "getInjectableDef", "getInjectorDef", diff --git a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json index 8f50f836903..b28bd07051e 100644 --- a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json +++ b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json @@ -228,6 +228,7 @@ "getDirectiveDef", "getFactoryDef", "getFirstLContainer", + "getInitialLViewFlagsFromDef", "getInjectImplementation", "getInjectableDef", "getInjectorDef", diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index c3a1ab10f27..4013f9f27d6 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -301,6 +301,7 @@ "getFactoryDef", "getFirstLContainer", "getFirstNativeNode", + "getInitialLViewFlagsFromDef", "getInjectImplementation", "getInjectableDef", "getInjectorDef",