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](../quick-start/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## Using NodeContainer to Mount Custom Nodes 22 23You can mount custom nodes under a **NodeContainer** using **NodeController**. 24 25```ts 26// common.ets 27import { BuilderNode, UIContext } from '@kit.ArkUI' 28 29class Params { 30 text: string = "this is a text" 31} 32 33let buttonNode: BuilderNode<[Params]> | null = null; 34 35@Builder 36function buttonBuilder(params: Params) { 37 Column() { 38 Button(params.text) 39 .fontSize(12) 40 .borderRadius(8) 41 .borderWidth(2) 42 .backgroundColor(Color.Orange) 43 } 44} 45 46export function createNode(uiContext: UIContext) { 47 buttonNode = new BuilderNode<[Params]>(uiContext); 48 buttonNode.build(wrapBuilder(buttonBuilder), { text: "This is a Button" }); 49 return buttonNode; 50} 51 52export function getOrCreateNode(uiContext: UIContext): BuilderNode<[Params]> | null { 53 if (buttonNode?.getFrameNode() && buttonNode?.getFrameNode()?.getUniqueId() != -1) { 54 return buttonNode; 55 } else { 56 return createNode(uiContext); 57 } 58} 59``` 60```ts 61// Index.ets 62import { FrameNode, NodeController, Size, UIContext } from '@kit.ArkUI' 63import { getOrCreateNode } from "./common" 64 65class MyNodeController extends NodeController { 66 private isShow: boolean = false; 67 68 constructor(isShow: boolean) { 69 super(); 70 this.isShow = isShow; 71 } 72 73 makeNode(uiContext: UIContext): FrameNode | null { 74 if (!this.isShow) { 75 return null; 76 } 77 let frameNode = getOrCreateNode(uiContext)?.getFrameNode(); 78 return frameNode ? frameNode : null; 79 } 80 81 aboutToResize(size: Size) { 82 console.log("aboutToResize width : " + size.width + " height : " + size.height) 83 } 84 85 aboutToAppear() { 86 console.log("aboutToAppear") 87 } 88 89 aboutToDisappear() { 90 console.log("aboutToDisappear"); 91 } 92 93 onTouchEvent(event: TouchEvent) { 94 console.log("onTouchEvent"); 95 } 96 97 toShow() { 98 this.isShow = true; 99 this.rebuild(); 100 } 101 102 toHide() { 103 this.isShow = false; 104 this.rebuild(); 105 } 106} 107 108@Entry 109@Component 110struct Index { 111 private myNodeController1: MyNodeController = new MyNodeController(true); 112 private myNodeController2: MyNodeController = new MyNodeController(false); 113 114 build() { 115 Column() { 116 NodeContainer(this.myNodeController1) 117 .width("100%") 118 .height("40%") 119 .backgroundColor(Color.Brown) 120 NodeContainer(this.myNodeController2) 121 .width("100%") 122 .height("40%") 123 .backgroundColor(Color.Gray) 124 Button("Change the place of button") 125 .onClick(() => { 126 // First, remove the node from the original placeholder node. 127 // Then, add the node to the new placeholder node. 128 // This ensures that the custom node exists only as the child of one node. 129 this.myNodeController1.toHide(); 130 this.myNodeController2.toShow(); 131 }) 132 } 133 .padding({ left: 35, right: 35, top: 35 }) 134 .width("100%") 135 .height("100%") 136 } 137} 138``` 139 140## Layout Differences Between Child Nodes Added Using NodeContainer and ContentSlot 141 142[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. 143 144```ts 145import { FrameNode, NodeContent, NodeController, typeNode, UIContext } from '@kit.ArkUI'; 146 147class NodeContentCtrl { 148 content: NodeContent 149 textNode: Array<typeNode.Text> = new Array(); 150 uiContext: UIContext 151 width: number 152 153 constructor(uiContext: UIContext) { 154 this.content = new NodeContent() 155 this.uiContext = uiContext 156 this.width = Infinity 157 } 158 159 AddNode() { 160 let node = typeNode.createNode(this.uiContext, "Text") 161 node.initialize("ContentText:" + this.textNode.length).fontSize(20) 162 this.textNode.push(node) 163 this.content.addFrameNode(node) 164 } 165 166 RemoveNode() { 167 let node = this.textNode.pop() 168 this.content.removeFrameNode(node) 169 } 170 171 RemoveFront() { 172 let node = this.textNode.shift() 173 this.content.removeFrameNode(node) 174 } 175 176 GetContent(): NodeContent { 177 return this.content 178 } 179} 180 181class MyNodeController extends NodeController { 182 public rootNode: FrameNode | null = null; 183 textNode: Array<typeNode.Text> = new Array(); 184 makeNode(uiContext: UIContext): FrameNode { 185 this.rootNode = new FrameNode(uiContext); 186 return this.rootNode; 187 } 188 189 AddNode(frameNode: FrameNode | null, uiContext: UIContext) { 190 let node = typeNode.createNode(uiContext, "Text") 191 node.initialize("ControllerText:" + this.textNode.length).fontSize(20) 192 this.textNode.push(node) 193 frameNode?.appendChild(node) 194 } 195 196 RemoveNode(frameNode: FrameNode | null) { 197 let node = this.textNode.pop() 198 frameNode?.removeChild(node) 199 } 200 201 RemoveFront(frameNode: FrameNode | null) { 202 let node = this.textNode.shift() 203 frameNode?.removeChild(node) 204 } 205} 206 207@Entry 208@Component 209struct Index { 210 @State message: string = 'Hello World'; 211 controller = new NodeContentCtrl(this.getUIContext()); 212 myNodeController = new MyNodeController(); 213 build() { 214 Row() { 215 Column() { 216 ContentSlot(this.controller.GetContent()) 217 Button("AddToSlot") 218 .onClick(() => { 219 this.controller.AddNode() 220 }) 221 Button("RemoveBack") 222 .onClick(() => { 223 this.controller.RemoveNode() 224 }) 225 Button("RemoveFront") 226 .onClick(() => { 227 this.controller.RemoveFront() 228 }) 229 } 230 .width('50%') 231 Column() { 232 NodeContainer(this.myNodeController) 233 Button("AddToNodeContainer") 234 .onClick(() => { 235 this.myNodeController.AddNode(this.myNodeController.rootNode, this.getUIContext()) 236 }) 237 Button("RemoveBack") 238 .onClick(() => { 239 this.myNodeController.RemoveNode(this.myNodeController.rootNode) 240 }) 241 Button("RemoveFront") 242 .onClick(() => { 243 this.myNodeController.RemoveFront(this.myNodeController.rootNode) 244 }) 245 } 246 .width('50%') 247 } 248 .height('100%') 249 } 250} 251``` 252 253 254