1# Custom Placeholder Nodes 2 3ArkUI provides two types of custom placeholder nodes: the built-in component [NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) and [ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md). They are used to display custom nodes and custom node trees. 4 5Unlike [NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), which acts as a container node with universal attributes, [ContentSlot](../ui/state-management/arkts-rendering-control-contentslot.md) is merely a semantic node and does not have universal attributes. It does not engage in layout and rendering processes. For hybrid development scenarios, the **ContentSlot** component is recommended when the container is an ArkTS component and the child component is created on the native side. For details, see [ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md). 6 7[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), a built-in component serving as a placeholder, comes with universal attributes, and its node layout follows the default top-left aligned [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md) component. 8 9[NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md) provides a set of lifecycle callbacks, including a [makeNode](../reference/apis-arkui/js-apis-arkui-nodeController.md#makenode) callback that returns the root node of a [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#framenode) tree. This [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) tree is then mounted under the corresponding [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md). In addition, NodeController provides the following callback methods: [aboutToAppear](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttoappear), [aboutToDisappear](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttodisappear), [aboutToResize](../reference/apis-arkui/js-apis-arkui-nodeController.md#abouttoresize), [onTouchEvent](../reference/apis-arkui/js-apis-arkui-nodeController.md#ontouchevent), and [rebuild](../reference/apis-arkui/js-apis-arkui-nodeController.md#rebuild), which are used to listen for the status of the associated [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md). 10 11For details about the callbacks, see [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md). 12 13> **NOTE** 14> 15> - Only custom FrameNodes and root nodes of component trees created by **BuilderNode** are supported under the **NodeContainer**. 16> 17> - Since API version 12, you can obtain a built-in component's proxy node through the query API of the FrameNode. This proxy node can be returned as the result of the **makeNode** callback, but it cannot be successfully mounted on the component tree, resulting in a failed display of the proxy node. 18> 19> - A node must be used as the child of only one parent node to avoid display or functional issues, particularly in page routing and animation scenarios. For example, if a single node is mounted on multiple **NodeContainer**s through **NodeController**, only one of the **NodeContainer**s will display the node. In addition, any updates to attributes such as visibility and opacity in any of these **NodeContainer**s, which can affect the child component state, will all influence the mounted child node. 20 21## Basic concepts 22 23- Custom node: node created using the APIs provided by ArkUI. Custom nodes include custom component nodes (FrameNode), custom render nodes (RenderNode), custom declarative nodes (BuilderNode), and [ComponentContent](../reference/apis-arkui/js-apis-arkui-ComponentContent.md). 24 25- Custom node tree: tree structure where the root node is a custom node. 26 27- Declarative node tree: tree structure where the root node is a declarative node. 28 29- Node tree: data structure that represents the hierarchical relationships between nodes. 30 31- Placeholder node: special node used in a declarative node tree to reserve a spot for a custom node tree. Placeholder nodes include **NodeContainer** and **ContentSlot**. As the main tree of a page is declarative, placeholder nodes are necessary to attach custom nodes built imperatively to the main tree. 32 33## Using NodeContainer to Mount Custom Nodes 34 35You can mount custom nodes under a **NodeContainer** using **NodeController**. 36 37```ts 38// common.ets 39import { BuilderNode, UIContext } from '@kit.ArkUI' 40 41class Params { 42 text: string = "this is a text" 43} 44 45let buttonNode: BuilderNode<[Params]> | null = null; 46 47@Builder 48function buttonBuilder(params: Params) { 49 Column() { 50 Button(params.text) 51 .fontSize(12) 52 .borderRadius(8) 53 .borderWidth(2) 54 .backgroundColor(Color.Orange) 55 } 56} 57 58export function createNode(uiContext: UIContext) { 59 buttonNode = new BuilderNode<[Params]>(uiContext); 60 buttonNode.build(wrapBuilder(buttonBuilder), { text: "This is a Button" }); 61 return buttonNode; 62} 63 64export function getOrCreateNode(uiContext: UIContext): BuilderNode<[Params]> | null { 65 if (buttonNode?.getFrameNode() && buttonNode?.getFrameNode()?.getUniqueId() != -1) { 66 return buttonNode; 67 } else { 68 return createNode(uiContext); 69 } 70} 71``` 72```ts 73// Index.ets 74import { FrameNode, NodeController, Size, UIContext } from '@kit.ArkUI' 75import { getOrCreateNode } from "./common" 76 77const TEST_TAG: string = "NodeContainer"; 78 79class MyNodeController extends NodeController { 80 private isShow: boolean = false; 81 82 constructor(isShow: boolean) { 83 super(); 84 this.isShow = isShow; 85 } 86 87 makeNode(uiContext: UIContext): FrameNode | null { 88 if (!this.isShow) { 89 return null; 90 } 91 let frameNode = getOrCreateNode(uiContext)?.getFrameNode(); 92 return frameNode ? frameNode : null; 93 } 94 95 aboutToResize(size: Size) { 96 console.log(TEST_TAG + " aboutToResize width : " + size.width + " height : " + size.height) 97 } 98 99 aboutToAppear() { 100 console.log(TEST_TAG + " aboutToAppear") 101 } 102 103 aboutToDisappear() { 104 console.log(TEST_TAG + " aboutToDisappear"); 105 } 106 107 onTouchEvent(event: TouchEvent) { 108 console.log(TEST_TAG + " onTouchEvent"); 109 } 110 111 toShow() { 112 this.isShow = true; 113 this.rebuild(); 114 } 115 116 toHide() { 117 this.isShow = false; 118 this.rebuild(); 119 } 120} 121 122@Entry 123@Component 124struct Index { 125 private myNodeController1: MyNodeController = new MyNodeController(true); 126 private myNodeController2: MyNodeController = new MyNodeController(false); 127 128 build() { 129 Column() { 130 NodeContainer(this.myNodeController1) 131 .width("100%") 132 .height("40%") 133 .backgroundColor(Color.Brown) 134 NodeContainer(this.myNodeController2) 135 .width("100%") 136 .height("40%") 137 .backgroundColor(Color.Gray) 138 Button("Change the place of button") 139 .onClick(() => { 140 // First, remove the node from the original placeholder node. 141 // Then, add the node to the new placeholder node. 142 // This ensures that the custom node exists only as the child of one node. 143 this.myNodeController1.toHide(); 144 this.myNodeController2.toShow(); 145 }) 146 } 147 .padding({ left: 35, right: 35, top: 35 }) 148 .width("100%") 149 .height("100%") 150 } 151} 152``` 153 154## Layout Differences Between Child Nodes Added Using NodeContainer and ContentSlot 155 156[NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) acts as a standard container that manages the layout of its child nodes. The child nodes added using **NodeContainer** follows the layout rules of the default top-left aligned [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md) component, instead of those of the parent container. On the other hand, [ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md) is a semantic node and does not engage in the layout process. Any child nodes added will be arranged according to the layout rules of the parent container. 157 158```ts 159import { FrameNode, NodeContent, NodeController, typeNode, UIContext } from '@kit.ArkUI'; 160 161class NodeContentCtrl { 162 content: NodeContent 163 textNode: Array<typeNode.Text> = new Array(); 164 uiContext: UIContext 165 width: number 166 167 constructor(uiContext: UIContext) { 168 this.content = new NodeContent() 169 this.uiContext = uiContext 170 this.width = Infinity 171 } 172 173 AddNode() { 174 let node = typeNode.createNode(this.uiContext, "Text") 175 node.initialize("ContentText:" + this.textNode.length).fontSize(20) 176 this.textNode.push(node) 177 this.content.addFrameNode(node) 178 } 179 180 RemoveNode() { 181 let node = this.textNode.pop() 182 this.content.removeFrameNode(node) 183 } 184 185 RemoveFront() { 186 let node = this.textNode.shift() 187 this.content.removeFrameNode(node) 188 } 189 190 GetContent(): NodeContent { 191 return this.content 192 } 193} 194 195class MyNodeController extends NodeController { 196 public rootNode: FrameNode | null = null; 197 textNode: Array<typeNode.Text> = new Array(); 198 199 makeNode(uiContext: UIContext): FrameNode { 200 this.rootNode = new FrameNode(uiContext); 201 return this.rootNode; 202 } 203 204 AddNode(frameNode: FrameNode | null, uiContext: UIContext) { 205 let node = typeNode.createNode(uiContext, "Text") 206 node.initialize("ControllerText:" + this.textNode.length).fontSize(20) 207 this.textNode.push(node) 208 frameNode?.appendChild(node) 209 } 210 211 RemoveNode(frameNode: FrameNode | null) { 212 let node = this.textNode.pop() 213 frameNode?.removeChild(node) 214 } 215 216 RemoveFront(frameNode: FrameNode | null) { 217 let node = this.textNode.shift() 218 frameNode?.removeChild(node) 219 } 220} 221 222@Entry 223@Component 224struct Index { 225 @State message: string = 'Hello World'; 226 controller = new NodeContentCtrl(this.getUIContext()); 227 myNodeController = new MyNodeController(); 228 229 build() { 230 Row() { 231 Column() { 232 ContentSlot(this.controller.GetContent()) 233 Button("AddToSlot") 234 .onClick(() => { 235 this.controller.AddNode() 236 }) 237 .margin(10) 238 Button("RemoveBack") 239 .onClick(() => { 240 this.controller.RemoveNode() 241 }) 242 .margin(10) 243 Button("RemoveFront") 244 .onClick(() => { 245 this.controller.RemoveFront() 246 }) 247 .margin(10) 248 } 249 .width('50%') 250 251 Column() { 252 NodeContainer(this.myNodeController) 253 Button("AddToNodeContainer") 254 .onClick(() => { 255 this.myNodeController.AddNode(this.myNodeController.rootNode, this.getUIContext()) 256 }) 257 .margin(10) 258 Button("RemoveBack") 259 .onClick(() => { 260 this.myNodeController.RemoveNode(this.myNodeController.rootNode) 261 }) 262 .margin(10) 263 Button("RemoveFront") 264 .onClick(() => { 265 this.myNodeController.RemoveFront(this.myNodeController.rootNode) 266 }) 267 .margin(10) 268 } 269 .width('50%') 270 } 271 .height('100%') 272 } 273} 274``` 275 276 277