1# BuilderNode 2 3## Overview 4 5The [BuilderNode](../reference/apis-arkui/js-apis-arkui-builderNode.md) provides the capability to mount built-in components. It creates component trees with a stateless UI method, the [global custom builder function](../quick-start/arkts-builder.md#global-custom-builder-function), which is decorated by @Builder, and accesses the root [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) through [getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode). This node can be directly returned by [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md) and mounted under the [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) node. It also enables embedding declarative component structures within **FrameNode** and [RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md) tree structures for mixed display capabilities. Additionally, the BuilderNode offers texture export functionality, with exported textures used for same-layer rendering in the [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md). 6 7The BuilderNode enables the combination of ArkTS built-in component trees with custom nodes (such as FrameNodes and RenderNodes) for mixed display. It also allows built-in components to be embedded within third-party frameworks that use custom nodes. 8 9The BuilderNode offers the capability to pre-create built-in components, enabling their initialization at customized times and dynamic mounting as needed in operations. This is particularly useful for reducing the initialization time of declarative components such as [Web](../reference/apis-arkweb/ts-basic-components-web.md) and [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), which typically take longer to initialize. 10 11 12 13A BuilderNode can be used only as a leaf node. If an update is required, you are advised to use the update APIs provided by the BuilderNode, rather than making modifications directly to the RenderNode obtained from it. 14 15> **NOTE** 16> 17> - The BuilderNode only supports a single [global custom build function(../quick-start/arkts-builder.md#global-custom-builder-function) decorated by @Builder and wrapped by [wrapBuilder](../quick-start/arkts-wrapBuilder.md). 18> 19> - A newly created BuilderNode can only obtain a **FrameNode** object pointing to the root node through [getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode) after [build](../reference/apis-arkui/js-apis-arkui-builderNode.md#build); otherwise, it returns **null**. 20> 21> - If the root node of the passed Builder is a syntactic node (such as **if/else** and **ForEach**), an additional FrameNode must be generated, which will be displayed as "BuilderProxyNode" in the node tree. 22> 23> - If BuilderNode mounts a node onto another FrameNode through **getFrameNode**, or mounts it as a child node onto a **NodeContainer**, the node uses the layout constraints of the parent component for layout. 24> 25> - If a BuilderNode's FrameNode mounts its node onto a RenderNode through [getRenderNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#getrendernode), its size defaults to **0** since its FrameNode is not yet part of the tree. To display it properly, you must explicitly specify the layout constraint size through [selfIdeaSize](../reference/apis-arkui/js-apis-arkui-builderNode.md#renderoptions) in the constructor. 26> 27> - Pre-creation with the BuilderNode does not reduce the creation time of components. For the **Web** component, resources must be loaded in the kernel during creation, and pre-creation cannot reduce this time. However, it enables the kernel to preload resources, which can reduce the loading time when the component is used. 28 29## Creating a BuilderNode Object 30 31When creating a **BuilderNode** object, which is a template class, you must specify a type that matches the type of the [WrappedBuilder](../quick-start/arkts-wrapBuilder.md) used in the **build** method later on. Mismatches can cause compilation warnings and failures. 32 33## Creating a Built-in Component Tree 34 35Use the **build** API of **BuilderNode** to create a built-in component tree. The tree is constructed based on the **WrappedBuilder** object passed in, and the root node of the component tree is retained. 36 37> **NOTE** 38> 39> Stateless UI methods using the global @Builder can have at most one root node. 40> 41> The @Builder within the **build** method accepts only one input parameter. 42> 43> In scenarios where @Builder is nested within another @Builder in the **build** method, ensure that the parameters of the nested @Builder match the input parameters provided to the **build** method. 44> 45> For scenarios where @Builder is nested within another @Builder, if the parameter types do not match, you must include the BuilderOptions](../reference/apis-arkui/js-apis-arkui-builderNode.md#buildoptions12) field as a parameter for the [build](../reference/apis-arkui/js-apis-arkui-builderNode.md#build12) method. 46 47Create offline nodes and built-in component trees, and use them in conjunction with FrameNodes. 48 49The root node of the BuilderNode is directly used as the return value of [makeNode](../reference/apis-arkui/js-apis-arkui-nodeController.md#makenode) of [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md). 50 51```ts 52import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI' 53 54class Params { 55 text: string = "" 56 57 constructor(text: string) { 58 this.text = text; 59 } 60} 61 62@Builder 63function buildText(params: Params) { 64 Column() { 65 Text(params.text) 66 .fontSize(50) 67 .fontWeight(FontWeight.Bold) 68 .margin({ bottom: 36 }) 69 } 70} 71 72class TextNodeController extends NodeController { 73 private textNode: BuilderNode<[Params]> | null = null; 74 private message: string = "DEFAULT"; 75 76 constructor(message: string) { 77 super(); 78 this.message = message; 79 } 80 81 makeNode(context: UIContext): FrameNode | null { 82 this.textNode = new BuilderNode(context); 83 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)) 84 return this.textNode.getFrameNode(); 85 } 86} 87 88@Entry 89@Component 90struct Index { 91 @State message: string = "hello" 92 93 build() { 94 Row() { 95 Column() { 96 NodeContainer(new TextNodeController(this.message)) 97 .width('100%') 98 .height(100) 99 .backgroundColor('#FFF0F0F0') 100 } 101 .width('100%') 102 .height('100%') 103 } 104 .height('100%') 105 } 106} 107``` 108 109When combining a BuilderNode with a RenderNode, note the following: 110 111If you mount the RenderNode from the BuilderNode under another RenderNode, you must explicitly specify [selfIdeaSize](../reference/apis-arkui/js-apis-arkui-builderNode.md#renderoptions) as the layout constraint for the BuilderNode. This approach to mounting nodes is not recommended. 112 113```ts 114import { NodeController, BuilderNode, FrameNode, UIContext, RenderNode } from "@kit.ArkUI" 115 116class Params { 117 text: string = "" 118 119 constructor(text: string) { 120 this.text = text; 121 } 122} 123 124@Builder 125function buildText(params: Params) { 126 Column() { 127 Text(params.text) 128 .fontSize(50) 129 .fontWeight(FontWeight.Bold) 130 .margin({ bottom: 36 }) 131 } 132} 133 134class TextNodeController extends NodeController { 135 private rootNode: FrameNode | null = null; 136 private textNode: BuilderNode<[Params]> | null = null; 137 private message: string = "DEFAULT"; 138 139 constructor(message: string) { 140 super(); 141 this.message = message; 142 } 143 144 makeNode(context: UIContext): FrameNode | null { 145 this.rootNode = new FrameNode(context); 146 let renderNode = new RenderNode(); 147 renderNode.clipToFrame = false; 148 this.textNode = new BuilderNode(context, { selfIdealSize: { width: 150, height: 150 } }); 149 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 150 const textRenderNode = this.textNode?.getFrameNode()?.getRenderNode(); 151 152 const rootRenderNode = this.rootNode.getRenderNode(); 153 if (rootRenderNode !== null) { 154 rootRenderNode.appendChild(renderNode); 155 renderNode.appendChild(textRenderNode); 156 } 157 158 return this.rootNode; 159 } 160} 161 162@Entry 163@Component 164struct Index { 165 @State message: string = "hello" 166 167 build() { 168 Row() { 169 Column() { 170 NodeContainer(new TextNodeController(this.message)) 171 .width('100%') 172 .height(100) 173 .backgroundColor('#FFF0F0F0') 174 } 175 .width('100%') 176 .height('100%') 177 } 178 .height('100%') 179 } 180} 181``` 182 183## Updating the Built-in Component Tree 184 185Create a built-in component tree using the **build** API of a **BuilderNode** object. The tree is constructed based on the **WrappedBuilder** object passed in, and the root node of the component tree is retained. 186 187Custom component updates follow the update mechanisms of [state management](../quick-start/arkts-state-management-overview.md). For custom components used directly in a **WrappedBuilder** object, their parent component is the **BuilderNode** object. Therefore, to update child components defined in the **WrappedBuilder** objects, you need to define the relevant state variables with the [@Prop](../quick-start/arkts-prop.md) or [@ObjectLink](../quick-start/arkts-observed-and-objectlink.md) decorator, in accordance with the specifications of state management and the needs of your application development. 188 189To update nodes within a BuilderNode: 190 191- Use the **update** API to update individual nodes within the BuilderNode. 192- Use the [updateConfiguration](../reference/apis-arkui/js-apis-arkui-builderNode.md#updateconfiguration12) API to trigger a full update of all nodes within the BuilderNode. 193 194 195 196```ts 197import { NodeController, BuilderNode, FrameNode, UIContext } from "@kit.ArkUI" 198 199class Params { 200 text: string = "" 201 constructor(text: string) { 202 this.text = text; 203 } 204} 205 206// Custom component 207@Component 208struct TextBuilder { 209 // The @Prop decorated attribute is the attribute to be updated in the custom component. It is a basic attribute. 210 @Prop message: string = "TextBuilder"; 211 212 build() { 213 Row() { 214 Column() { 215 Text(this.message) 216 .fontSize(50) 217 .fontWeight(FontWeight.Bold) 218 .margin({ bottom: 36 }) 219 .backgroundColor(Color.Gray) 220 } 221 } 222 } 223} 224 225@Builder 226function buildText(params: Params) { 227 Column() { 228 Text(params.text) 229 .fontSize(50) 230 .fontWeight(FontWeight.Bold) 231 .margin({ bottom: 36 }) 232 TextBuilder({ message: params.text }) // Custom component 233 } 234} 235 236class TextNodeController extends NodeController { 237 private textNode: BuilderNode<[Params]> | null = null; 238 private message: string = ""; 239 240 constructor(message: string) { 241 super() 242 this.message = message 243 } 244 245 makeNode(context: UIContext): FrameNode | null { 246 this.textNode = new BuilderNode(context); 247 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)) 248 return this.textNode.getFrameNode(); 249 } 250 251 update(message: string) { 252 if (this.textNode !== null) { 253 // Call update to perform an update. 254 this.textNode.update(new Params(message)); 255 } 256 } 257} 258 259@Entry 260@Component 261struct Index { 262 @State message: string = "hello" 263 private textNodeController: TextNodeController = new TextNodeController(this.message); 264 private count = 0; 265 266 build() { 267 Row() { 268 Column() { 269 NodeContainer(this.textNodeController) 270 .width('100%') 271 .height(200) 272 .backgroundColor('#FFF0F0F0') 273 Button('Update') 274 .onClick(() => { 275 this.count += 1; 276 const message = "Update " + this.count.toString(); 277 this.textNodeController.update(message); 278 }) 279 } 280 .width('100%') 281 .height('100%') 282 } 283 .height('100%') 284 } 285} 286``` 287 288## Canceling the Reference to the Entity Node 289 290A **BuilderNode** object is mapped to a backend entity node, and its memory release is usually contingent on the disposal of the frontend object. To directly release the backend node object, you can call the [dispose](../reference/apis-arkui/js-apis-arkui-builderNode.md#dispose12) API to break the reference to the entity node. Once this is done, the frontend **BuilderNode** object will no longer affect the lifecycle of the entity node. 291 292> **NOTE** 293> 294> Calling **dispose** on a **BuilderNode** object breaks its reference to the backend entity node, and also simultaneously severs the references of its contained FrameNode and RenderNode to their respective entity nodes. 295 296## Injecting a Touch Event 297 298Use the [postTouchEvent](../reference/apis-arkui/js-apis-arkui-builderNode.md#posttouchevent) API in the BuilderNode to inject a [touch event](../reference/apis-arkui/arkui-ts/ts-universal-events-touch.md) into the bound component for event simulation and forwarding. 299 300The following example forwards a touch event from one **Column** component to another in the BuilderNode, so that when the lower **Column** component is touched, the upper **Column** component also receives the same touch event. The API returns **true** if the button's event is successfully recognized. 301 302```ts 303import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI'; 304 305class Params { 306 text: string = "this is a text" 307} 308 309@Builder 310function ButtonBuilder(params: Params) { 311 Column() { 312 Button(`button ` + params.text) 313 .borderWidth(2) 314 .backgroundColor(Color.Orange) 315 .width("100%") 316 .height("100%") 317 .gesture( 318 TapGesture() 319 .onAction((event: GestureEvent) => { 320 console.log("TapGesture"); 321 }) 322 ) 323 } 324 .width(500) 325 .height(300) 326 .backgroundColor(Color.Gray) 327} 328 329class MyNodeController extends NodeController { 330 private rootNode: BuilderNode<[Params]> | null = null; 331 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(ButtonBuilder); 332 333 makeNode(uiContext: UIContext): FrameNode | null { 334 this.rootNode = new BuilderNode(uiContext); 335 this.rootNode.build(this.wrapBuilder, { text: "this is a string" }) 336 return this.rootNode.getFrameNode(); 337 } 338 339 postTouchEvent(touchEvent: TouchEvent): void { 340 if (this.rootNode == null) { 341 return; 342 } 343 let result = this.rootNode.postTouchEvent(touchEvent); 344 console.log("result " + result); 345 } 346} 347 348@Entry 349@Component 350struct MyComponent { 351 private nodeController: MyNodeController = new MyNodeController(); 352 353 build() { 354 Column() { 355 NodeContainer(this.nodeController) 356 .height(300) 357 .width(500) 358 Column() 359 .width(500) 360 .height(300) 361 .backgroundColor(Color.Pink) 362 .onTouch((event) => { 363 if (event != undefined) { 364 this.nodeController.postTouchEvent(event); 365 } 366 }) 367 } 368 } 369} 370``` 371<!--no_check-->